WebGL-визуализация: шейдеры, частицы и оптимизация

Посетитель заходит на страницу с картой, а браузер начинает тормозить: скролл дёргается, Tooltip появляется через секунду. Если вы сталкивались с таким — данные слишком большие для SVG или Canvas 2D. WebGL решает эту проблему: он рендерит сотни тысяч точек за один draw call, используя GPU клиента. П

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
WebGL-визуализация: шейдеры, частицы и оптимизация
Сложный
~2-4 недели

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1414
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    982
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    995

Посетитель заходит на страницу с картой, а браузер начинает тормозить: скролл дёргается, Tooltip появляется через секунду. Если вы сталкивались с таким — данные слишком большие для SVG или Canvas 2D. WebGL решает эту проблему: он рендерит сотни тысяч точек за один draw call, используя GPU клиента. При 500 000+ точек Canvas 2D падает до 3 fps, а WebGL держит 60 fps. Это не просто улучшение — это возможность создавать дашборды реального времени, которые раньше были невозможны.

Однажды к нам пришёл проект по визуализации геоданных для логистической компании. У них было 800 тысяч точек с координатами и весами. Canvas 2D выдавал 5 кадров в секунду. Мы переписали всё на WebGL — получили стабильные 60 fps. Заказчик сэкономил на железе и ускорил загрузку дашборда в 12 раз.

Мы специализируемся на WebGL-визуализациях более 10 лет. За это время реализовали десятки проектов: от карт с миллионами объектов до анимированных particle-эффектов. Наш опыт гарантирует, что визуализация будет работать плавно на любых устройствах — от десктопа до mid-range мобильных.

Как WebGL обеспечивает 60 fps на больших данных?

WebGL использует GPU и минимизирует количество draw calls. В отличие от Canvas 2D, который отрисовывает каждую точку отдельно, WebGL передаёт весь массив данных одним вызовом. Это снижает накладные расходы CPU и позволяет достичь 60 fps даже при 1 млн точек. Ключевые факторы: использование буферов с типом float, отказ от избыточных uniform-ов, и правильная настройка blending. Согласно WebGL API, каждый draw call может обрабатывать до 2^32 вершин, что даёт огромный запас производительности.

Когда нужен именно WebGL

  • Scatter plot с 500 000+ точек (финансовые данные, геопространственные)
  • Particle systems: интерактивные фоны, визуализации физических процессов
  • Heatmaps в реальном времени (данные биржевых торгов, тепловые карты кликов)
  • Процедурные анимации (шум Перлина, математические поверхности)
  • Обработка изображений через шейдеры (фильтры, эффекты)

Для стандартных графиков (100–10 000 точек) — D3.js или Recharts достаточно.

Сравнение WebGL, Canvas 2D и SVG для больших данных

Параметр WebGL Canvas 2D SVG
Макс. количество точек (60 fps) 1 000 000+ 10 000 1 000
Тип рендеринга GPU, draw call CPU, immediate DOM, retained
Пиковая производительность 60 fps ~30 fps при 50k ~15 fps при 10k
Сложность реализации Высокая Средняя Низкая
Поддержка старых браузеров WebGL1 fallback Хорошая Отличная

WebGL в 10 раз быстрее Canvas 2D при 500k точках — это разница между 60 fps и 3 fps. Выбор инструмента зависит от объёма данных и требований к визуализации.

Сравнение популярных библиотек WebGL

Библиотека Типичные случаи Сложность написания шейдеров Производительность
deck.gl Геоданные, scatter, heatmap Низкая (готовые слои) Высокая для 2D
regl Кастомные эффекты, particles Средняя Высокая
Three.js 3D-сцены, анимации Высокая Высокая для 3D
raw WebGL2 Максимальная гибкость Высокая Максимальная

Как выбрать между deck.gl и кастомными шейдерами?

Для геопространственных данных (карты, scatter plots, heatmaps) используем deck.gl — библиотека от Uber, оптимизированная для больших датасетов. Она предоставляет готовые Layer-компоненты (ScatterplotLayer, HeatmapLayer, ColumnLayer) и поддерживает WebGL2. Для кастомных визуализаций (particle-системы, процедурная графика) пишем шейдеры напрямую или через обёртку regl.

deck.gl: визуализация геопространственных данных

npm install deck.gl @deck.gl/layers @deck.gl/react react-map-gl maplibre-gl 
import DeckGL from '@deck.gl/react' import { ScatterplotLayer, HeatmapLayer, ColumnLayer } from '@deck.gl/layers' import Map from 'react-map-gl/maplibre' interface DataPoint { coordinates: [number, number] value: number category: string } function GeoVisualization({ data }: { data: DataPoint[] }) { const [viewState, setViewState] = useState({ longitude: 37.6, latitude: 55.75, zoom: 10, pitch: 45, bearing: 0, }) const layers = [ new ScatterplotLayer({ id: 'scatter', data, getPosition: (d) => d.coordinates, getRadius: (d) => Math.sqrt(d.value) * 10, getFillColor: (d) => { const t = d.value / 1000 return [255 * t, 100, 255 * (1 - t), 200] }, pickable: true, radiusMinPixels: 2, radiusMaxPixels: 50, }), new HeatmapLayer({ id: 'heatmap', data, getPosition: (d) => d.coordinates, getWeight: (d) => d.value, radiusPixels: 40, intensity: 1, threshold: 0.1, colorRange: [ [0, 0, 255, 0], [0, 255, 255, 128], [0, 255, 0, 200], [255, 255, 0, 220], [255, 0, 0, 255], ], }), ] return ( <DeckGL viewState={viewState} onViewStateChange={({ viewState }) => setViewState(viewState as any)} layers={layers} getTooltip={({ object }: { object: DataPoint }) => object && { html: `<b>Значение:</b> ${object.value}`, style: { background: '#fff' } } } style={{ position: 'relative', height: '600px' }} controller={true} > <Map mapStyle="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json" /> </DeckGL> ) } 

WebGL шейдеры напрямую: GLSL

function WebGLCanvas() { const canvasRef = useRef<HTMLCanvasElement>(null) useEffect(() => { const canvas = canvasRef.current! const gl = canvas.getContext('webgl2')! const vertexShaderSrc = `#version 300 es in vec2 a_position; in float a_value; out float v_value; uniform vec2 u_resolution; void main() { vec2 zeroToOne = a_position / u_resolution; vec2 zeroToTwo = zeroToOne * 2.0; vec2 clipSpace = zeroToTwo - 1.0; gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1); gl_PointSize = max(2.0, sqrt(a_value) * 3.0); v_value = a_value; } ` const fragmentShaderSrc = `#version 300 es precision highp float; in float v_value; out vec4 outColor; vec3 viridis(float t) { const vec3 c0 = vec3(0.267, 0.004, 0.329); const vec3 c1 = vec3(0.127, 0.566, 0.551); const vec3 c2 = vec3(0.993, 0.906, 0.144); return mix(mix(c0, c1, t), mix(c1, c2, t), t); } void main() { vec2 coord = gl_PointCoord - 0.5; if (length(coord) > 0.5) discard; outColor = vec4(viridis(v_value), 0.8); } ` function createShader(type: number, source: string): WebGLShader { const shader = gl.createShader(type)! gl.shaderSource(shader, source) gl.compileShader(shader) if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { throw new Error(gl.getShaderInfoLog(shader) ?? 'Shader error') } return shader } const program = gl.createProgram()! gl.attachShader(program, createShader(gl.VERTEX_SHADER, vertexShaderSrc)) gl.attachShader(program, createShader(gl.FRAGMENT_SHADER, fragmentShaderSrc)) gl.linkProgram(program) const N = 100_000 const positions = new Float32Array(N * 2) const values = new Float32Array(N) for (let i = 0; i < N; i++) { positions[i * 2] = Math.random() * canvas.width positions[i * 2 + 1] = Math.random() * canvas.height values[i] = Math.random() } const posBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer) gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW) const aPosition = gl.getAttribLocation(program, 'a_position') gl.enableVertexAttribArray(aPosition) gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0) const valBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, valBuffer) gl.bufferData(gl.ARRAY_BUFFER, values, gl.STATIC_DRAW) const aValue = gl.getAttribLocation(program, 'a_value') gl.enableVertexAttribArray(aValue) gl.vertexAttribPointer(aValue, 1, gl.FLOAT, false, 0, 0) gl.useProgram(program) gl.uniform2f(gl.getUniformLocation(program, 'u_resolution'), canvas.width, canvas.height) gl.clearColor(0.05, 0.05, 0.1, 1) gl.clear(gl.COLOR_BUFFER_BIT) gl.enable(gl.BLEND) gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) gl.drawArrays(gl.POINTS, 0, N) }, []) return <canvas ref={canvasRef} width={800} height={600} /> } 

Regl: удобная обёртка над WebGL

npm install regl npm install -D @types/regl 
import createREGL from 'regl' const regl = createREGL(canvasRef.current!) const drawParticles = regl({ vert: ` precision mediump float; attribute vec2 position; attribute float age; uniform float time; void main() { vec2 pos = position + vec2(cos(time + age), sin(time * 0.7 + age)) * 0.05; gl_Position = vec4(pos, 0, 1); gl_PointSize = 3.0; } `, frag: ` precision mediump float; void main() { gl_FragColor = vec4(0.4, 0.8, 1.0, 0.7); } `, attributes: { position: particlePositions, age: particleAges, }, uniforms: { time: regl.context('time'), }, count: PARTICLE_COUNT, primitive: 'points', }) regl.frame(({ time }) => { regl.clear({ color: [0, 0, 0.1, 1], depth: 1 }) drawParticles() }) 
Что входит в работу
  • Анализ данных и выбор подходящей технологии (deck.gl, raw WebGL, regl)
  • Реализация визуализации с учётом производительности (асинхронная загрузка, LOD)
  • Оптимизация под 60 fps на mid-range мобильных устройствах
  • Написание кастомных шейдеров, если требуется
  • Документация по интеграции и использованию
  • Обучение команды заказчика работе с кодом
  • Бесплатная поддержка 14 дней после сдачи

Процесс работы

  1. Аналитика — изучаем объём и структуру данных, определяем тип визуализации
  2. Проектирование — выбираем библиотеку или raw WebGL, проектируем шейдеры
  3. Реализация — пишем код, интегрируем с CMS/React/Next.js
  4. Тестирование — проверяем производительность на десктопе, планшете, мобильном
  5. Деплой — публикуем, настраиваем CDN для текстур и данных

Сроки ориентировочно

  • Базовая визуализация с deck.gl (scatter plot, heatmap) — 3–4 дня
  • Кастомные шейдеры и particle-системы — 7–10 дней
  • Комплексная визуализация с несколькими слоями и анимацией — до 3 недель

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

Типичные ошибки при внедрении WebGL

  • Использовать WebGL для простых графиков (до 10k точек) — избыточно, подойдёт Canvas 2D.
  • Не учитывать производительность на мобильных устройствах — mid-range телефоны часто имеют слабое GPU.
  • Забывать про fallback на WebGL1 для Safari <15 или старых Android.

Закажите WebGL-визуализацию — получите консультацию в течение дня. Наши инженеры с 10+ летним опытом помогут выбрать оптимальное решение и реализуют проект под ключ. Получите консультацию по вашему проекту — свяжитесь с нами для обсуждения.