Написание собственного шейдерного пасса (ShaderPass)

В Three.js система постобработки реализована через EffectComposer, который управляет последовательностью рендер-пассов. Каждый пасс — это отдельный шаг обработки изображения. ShaderPass позволяет применять пользовательские шейдеры к сцене после её рендеринга, создавая эффекты вроде размытия, цветокоррекции или искажений.

ShaderPass представляет собой обёртку вокруг материала типа ShaderMaterial, где задаются шейдеры вершин (vertexShader) и фрагментов (fragmentShader), а также униформы (uniforms), через которые можно передавать данные в GPU.

import * as THREE from 'three';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';

// Основная сцена и камера
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

// Рендерер
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Постобработка
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);

Структура пользовательского ShaderPass

Любой шейдерный пасс строится по стандартной схеме:

  1. Униформы — глобальные переменные шейдера. Могут включать текстуры, параметры времени, разрешение экрана.
  2. Вершинный шейдер — отвечает за позицию вершин и передачу данных во фрагментный шейдер.
  3. Фрагментный шейдер — вычисляет цвет каждого пикселя.
  4. Материал ShaderMaterial — связывает вершинный и фрагментный шейдер с униформами.
  5. ShaderPass — использует этот материал для обработки сцены.

Пример простого эффекта размытия:

const BlurShader = {
  uniforms: {
    'tDiffuse': { value: null },
    'resolution': { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
    'blurAmount': { value: 1.0 }
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform vec2 resolution;
    uniform float blurAmount;
    varying vec2 vUv;

    void main() {
      vec4 color = vec4(0.0);
      float total = 0.0;
      for (float x = -4.0; x <= 4.0; x++) {
        for (float y = -4.0; y <= 4.0; y++) {
          vec2 offset = vec2(x, y) * blurAmount / resolution;
          color += texture2D(tDiffuse, vUv + offset);
          total += 1.0;
        }
      }
      gl_FragColor = color / total;
    }
  `
};

const blurPass = new ShaderPass(new THREE.ShaderMaterial({
  uniforms: BlurShader.uniforms,
  vertexShader: BlurShader.vertexShader,
  fragmentShader: BlurShader.fragmentShader
}));

composer.addPass(blurPass);

Управление параметрами шейдера

Униформы можно динамически обновлять каждый кадр. Это позволяет анимировать эффекты, например:

function animate(time) {
  requestAnimationFrame(animate);
  blurPass.material.uniforms['blurAmount'].value = 1.0 + 0.5 * Math.sin(time * 0.001);
  composer.render();
}
animate();

Многопассовая обработка

EffectComposer поддерживает последовательное добавление нескольких пассов. Последние пассы можно пометить свойством renderToScreen = true, чтобы их результат выводился на экран:

const colorShiftPass = new ShaderPass(ColorShiftShader);
colorShiftPass.renderToScreen = true;
composer.addPass(colorShiftPass);

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

Оптимизация ShaderPass

  • Использование меньшего разрешения (THREE.WebGLRenderTarget) для промежуточных пассов снижает нагрузку GPU.
  • Минимизация циклов во фрагментных шейдерах: каждая итерация увеличивает время рендера.
  • Адаптация шейдера под THREE.UniformsUtils.clone() для повторного использования без глобального конфликта униформ.

Примеры расширенных эффектов

  1. Геометрическая деформация: можно использовать координаты vUv и шумовые текстуры для создания волновых эффектов.
  2. Цветокоррекция и LUT: передача цветовой карты через униформу для сложной градации цвета.
  3. Боке и размытие в глубине сцены: комбинирование с глубинной картой через THREE.DepthTexture.

Интеграция с существующей сценой

ShaderPass легко комбинируется с другими эффектами Three.js, включая OutlinePass, BloomPass или пользовательские шейдеры с RawShaderMaterial. Главное — корректно передавать tDiffuse и другие текстуры в униформы, а также следить за порядком пассов, так как они влияют на итоговый результат.

Эффективное использование ShaderPass открывает возможности создавать уникальные визуальные эффекты, выходящие за пределы стандартного рендеринга Three.js.