Разработка шейдеров постобработки изображения игр
Финальный кадр в игровом рендере — это не то, что выходит из геометрического пайплайна. Между G-Buffer и экраном стоит стек постобработки, и именно он определяет, есть ли у картинки характер или нет. Bloom с неправильным threshold размывает хайлайты в белую кашу. Chromatic aberration с фиксированным offset выглядит как артефакт, а не стилистика. Самописный шейдер depth of field, который не учитывает CoC (circle of confusion) корректно, даёт характерную «замыленность» на границах переднего плана.
Это не абстрактные проблемы — это то, что отличает проект с нормальным визуальным стеком от проекта, где «что-то не так, но не понятно что».
Где ломается стандартный стек постобработки
Unity URP/HDRP и Unreal Engine оба предоставляют Volume Framework для постобработки — но у каждого есть зоны, где стандартные эффекты не покрывают потребности конкретного визуального стиля.
В URP самая частая боль — отсутствие нативного поддержания кастомных пассов в Renderer Feature без глубокого понимания ScriptableRenderPass и порядка выполнения в RenderPassEvent. Разработчики вставляют кастомный Blit в AfterRenderingPostProcessing и получают эффект, который накладывается поверх URP-овского стека вместо интеграции внутрь него — в результате TAA и FXAA либо не работают с кастомным материалом, либо двоят артефакты.
В HDRP ситуация другая: там Custom Pass работает через CustomPassVolume, и если шейдер использует _CameraDepthTexture без явного объявления в TEXTURE2D + SAMPLER под HDRP-макросами, он просто рендерит чёрный экран на некоторых платформах — без предупреждений в консоли.
Unreal-специфика — другая. Там постобработка через Material с доменом Post Process работает быстро до первого столкновения с r.PostProcessAAQuality и тем, как TAA взаимодействует с кастомными SceneTexture нодами. Если шейдер читает PostProcessInput0 без учёта jitter offset, на движущейся камере появляются «призраки» — ghosting, который выглядит как баг рендерера, хотя причина в шейдере.
Что реально делается в рамках этой услуги
Разработка постпроцессингового шейдера — это не «написать HLSL». Это цикл из нескольких этапов, у каждого из которых свои инструменты.
Анализ визуального рефернса и постановка задачи. До написания первой строки кода — понять, что именно нужно воспроизвести. Cel-shading с edge detection через Sobel на глубине и нормалях отличается от cel-shading через Step в освещении. Это разные шейдеры с разными trade-off по производительности.
Прототипирование в Shader Graph / Material Editor. ShaderGraph в Unity позволяет быстро проверить гипотезу без компиляции вручную. Для Unreal — Material Graph. Прототип делается за 1-2 дня, после чего принимается решение: оставить в нодовой системе или переписать в HLSL/Custom HLSL node для оптимизации.
Написание и оптимизация HLSL/GLSL. Финальный шейдер пишется с учётом платформы. Мобильный таргет — это mediump float, минимум текстурных сэмплов, отсутствие discard. PC/консоль — можно позволить fullscreen pass с несколькими Blit и compute-шейдером для separation pass в Gaussian blur.
Интеграция в пайплайн. ScriptableRenderPass для URP, CustomPassVolume для HDRP, Post Process Material для Unreal. Включая настройку Injection Point, порядка выполнения и совместимости с Motion Vectors если нужен TAA.
Профилирование. RenderDoc для frame capture, Unity Frame Debugger / Unreal RenderDoc plugin для анализа пассов. GPU Profiler для измерения времени конкретного пасса. На мобильных — Snapdragon Profiler или Mali Graphics Debugger.
На одном из проектов — мобильная RPG в URP — заказчик хотел кастомный эффект «чернильного контура» поверх геометрии. Первый прототип через ScriptableRenderPass с Roberts Cross на depth давал 4ms на Adreno 650 при 1080p. После переписки на single-pass с упрощённым ядром и использованием _CameraDepthNormalsTexture вместо двух отдельных текстур — 1.1ms. Разница не в алгоритме, а в количестве текстурных фетчей.
Этапы работы над шейдером
| Этап | Что делается | Примерные сроки |
|---|---|---|
| Разбор референса и ТЗ | Анализ визуального стиля, определение алгоритма | 1–2 дня |
| Прототип в ShaderGraph / Material Graph | Быстрая проверка гипотезы, согласование | 1–3 дня |
| HLSL-реализация | Написание шейдера под целевую платформу | 2–7 дней |
| Интеграция в рендер-пайплайн | ScriptableRenderPass / CustomPassVolume / PP Material | 1–3 дня |
| Профилирование и оптимизация | Frame capture, GPU timing, оптимизация под таргет | 1–4 дня |
| Документация и передача | Комментарии в коде, инструкция по настройке | 0.5–1 день |
Сложные эффекты с несколькими пассами (например, volumetric fog через ray marching в постпроцессе или screen-space subsurface scattering) могут занимать 3–4 недели с учётом итераций.
Типичные ошибки при самостоятельной разработке
Большинство проблем — не в алгоритме, а в интеграции.
Неправильный RenderPassEvent. Вставка кастомного пасса в BeforeRenderingPostProcessing без понимания того, что URP на этом этапе ещё не применил Color Grading — в результате LUT накладывается поверх кастомного эффекта, а не под ним.
Игнорирование camera stacking. В URP при Overlay Camera кастомный ScriptableRenderPass, подключённый к Base Camera, не выполняется для Overlay — нужно регистрировать пасс в обоих Renderer Asset или использовать Universal Renderer с правильным флагом renderingLayerMask.
Хардкод разрешения. float2(1.0/1920.0, 1.0/1080.0) в шейдере вместо _ScreenParams.zw - 1 — на устройствах с нестандартным разрешением или при dynamic resolution эффект ломается.
Потеря HDR. Если эффект применяется после tonemap, а рассчитан для линейного HDR-буфера — цвета будут некорректные. Важно явно определить точку применения в пайплайне.
Стоимость разработки рассчитывается после анализа технического задания: платформа, целевой рендер-пайплайн, сложность алгоритма и требования к производительности.





