Создание кастомного постпроцесса на GLSL

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


Инициализация сцены и базового постпроцесса

Создание кастомного постпроцесса начинается с базовой сцены:

const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);

const camera = new BABYLON.ArcRotateCamera(
    "camera", Math.PI / 2, Math.PI / 4, 10, BABYLON.Vector3.Zero(), scene
);
camera.attachControl(canvas, true);

const light = new BABYLON.HemisphericLight(
    "light", new BABYLON.Vector3(0, 1, 0), scene
);

const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2 }, scene);

После создания сцены можно перейти к подключению кастомного постпроцесса. Ключевым элементом является BABYLON.PostProcess, который позволяет интегрировать GLSL-шейдер в рендеринг сцены.


Создание кастомного шейдера

Шейдер для постпроцесса пишется на GLSL. Фрагментный шейдер получает в качестве входных данных текстуру сцены (textureSampler) и координаты пикселя (uv).

Пример простого шейдера, создающего эффект инверсии цветов:

precision highp float;

varying vec2 vUV;
uniform sampler2D textureSampler;

void main(void) {
    vec4 color = texture2D(textureSampler, vUV);
    gl_FragColor = vec4(1.0 - color.rgb, color.a);
}
  • vUV — нормализованные координаты текущего пикселя.
  • texture2D(textureSampler, vUV) — выборка цвета пикселя из исходной сцены.
  • gl_FragColor — итоговый цвет, который будет отображен на экране.

Подключение шейдера через Babylon.js

Для создания постпроцесса необходимо определить объект PostProcess и передать шейдер:

const customPostProcess = new BABYLON.PostProcess(
    "customPost",             // имя постпроцесса
    "customShader",           // имя шейдера без расширения .fragment.fx
    ["time"],                 // массив uniform-переменных
    null,                     // параметры текстур (если нужны)
    1.0,                      // ratio рендеринга (1.0 — полный размер)
    camera                    // камера, к которой применяется постпроцесс
);

Uniform-переменные можно обновлять на каждом кадре с помощью метода onApply:

let startTime = performance.now();

customPostProcess.onAp ply = (effect) => {
    let currentTime = (performance.now() - startTime) / 1000.0;
    effect.setFloat("time", currentTime);
};

Создание более сложных эффектов

Сдвиг по цветам и глитч-эффект

Шейдер можно усложнить, вводя манипуляции с координатами uv:

precision highp float;

varying vec2 vUV;
uniform sampler2D textureSampler;
uniform float time;

void main(void) {
    vec2 offset = vec2(sin(time * 3.0) * 0.02, cos(time * 2.0) * 0.02);
    vec4 color = texture2D(textureSampler, vUV + offset);
    gl_FragColor = vec4(color.b, color.g, color.r, color.a);
}
  • offset позволяет сдвигать пиксели по синусоидальному закону.
  • Перестановка компонентов color.b, color.g, color.r создаёт эффект глитча.

Использование нескольких постпроцессов

Babylon.js поддерживает цепочки постпроцессов. Их порядок влияет на финальный результат:

const blurPostProcess = new BABYLON.PostProcess(
    "blur", "blurShader", ["blurAmount"], null, 1.0, camera
);

blurPostProcess.onAp ply = (effect) => {
    effect.setFloat("blurAmount", 0.005);
};

camera.attachPostProcess(customPostProcess);
camera.attachPostProcess(blurPostProcess);
  • Постпроцесс customPostProcess выполняется перед blurPostProcess.
  • Такой подход позволяет комбинировать эффекты, создавая уникальные визуальные решения.

Оптимизация шейдеров

Для сложных сцен важно учитывать производительность:

  1. Минимизировать количество операций на пиксель.
  2. Использовать precision mediump float, если не нужны высокие значения точности.
  3. Сократить вызовы texture2D за кадр.
  4. Проверять совместимость с мобильными устройствами, где шейдеры с высоким количеством циклов могут сильно тормозить.

Работа с текстурами и буферами

Кастомный постпроцесс может получать дополнительные текстуры:

const postProcess = new BABYLON.PostProcess(
    "customTexture",
    "shaderWithTexture",
    ["time"],
    ["extraTexture"],
    1.0,
    camera
);

postProcess.onAp ply = (effect) => {
    effect.setTexture("extraTexture", myTexture);
};
  • Можно подключать цветовые карты, шумовые текстуры, карты глубины.
  • Использование нескольких текстур позволяет создавать эффекты тонирования, имитации пленки и др.

Важные особенности GLSL в Babylon.js

  • varying vec2 vUV — ключевая переменная для координат текстуры.
  • Uniform-переменные обновляются на каждом кадре через effect.setFloat, effect.setVector2 и т.д.
  • Babylon.js автоматически создаёт vertex shader, но при необходимости можно использовать кастомный vertex shader.

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