О графике в Unity: Графический конвейер

от автора

Всем привет! Меня зовут Григорий Дядиченко, я уже что-то разрабатываю на Unity десять лет. Давно ничего не писал, и тут собрался с силами и решил, что хочу написать про компьютерную графику. А точнее пройтись по её базе в контексте Unity. Если интересуетесь темой — добро пожаловать под кат!

Всего я в разработке 10 лет (даже чуть больше). Но последние 8 лет я занимаюсь разработкой под заказ. Поэтому задачи у меня довольно разноплановые, но довольно часто это связано с крутой графикой на слабых устройствах. Будь то AR, VR или мобилки. Я когда-то писал статью про оптимизированный для мобилок акрил скажем. Так как проектов у меня обычно много, то часто я отдаю какие-то работы на подряд. И если на игровую логику, вёрстку, адаптивные интерфейсы подрядчиков бывает найти не трудно, то что-то действительно сложно по графике могут сделать единицы. Да и многие плавают даже в базе того как работают графические чипы, видеопамять, да и рендер в целом.

Хочется составить набор статей, который покроет основы компьютерной графики в контексте Unity. Свои рендереры на плюсах, CPU рендеринг и подобные темы я разбирать не хочу, а вот что как и почему работает в движке можно обсудить. И для этого я придумал следующий набор тем, чтобы удобно добавить в закладки и база была под рукой:

1. О графическом конвейере (о работе графического конвейера)
2. О видеокартах (архитектура GPU, работа параллельных вычислений, Warps/Wavefronts, branch divergence, texture fetch)
3. О линейной алгебре (матрицы, пространственные преобразования, проекции, системы координат)
4. О цвете и свете (цветовые пространства, модели освещения, тени)
5. О шейдерах (работа с вертексами, развёртки, оптимизация)
6. О compute шейдерах (симуляция частиц, обработка изображений)
7. О оптимизациях (батчинг, SRP батчер, GPU инстансинг)

Если я о чём-то забыл или что-то ещё интересно — напишите в комментариях. Итак, начнём с простого, о чём написано уже много, поэтому эта статья больше вводная.

Что такое графический конвейер?

Каждый кадр в видеоигре, 3D-приложении или даже интерфейсе — это результат работы графического конвейера (rendering pipeline). Это процесс, в котором геометрия сцены превращается в пиксели на экране. Графический конвейер — это последовательность этапов, через которые проходят 3D-объекты, чтобы превратиться в 2D-изображение. С точки зрения компьютера 2д объекты так же проходят через этот конвейер.

Основные этапы

  1. Вершинный шейдер
    Обработки вершин

  2. Геометрический шейдер (опционально)
    Модификация и генерация новых примитивов

  3. Клипинг
    Отсечение невидимых частей объектов

  4. Растеризация
    Преобразование геометрии в пиксели

  5. Фрагментный шейдер
    Расчёт цвета пикселей

  6. Тесты и смешивание
    Работа с Depth, Stencil, Alpha Blending

Вершинный шейдер

Вершинный шейдер (Vertex Shader) — это программа, выполняемая на графическом процессоре (GPU) на этапе графического конвейера, которая обрабатывает каждую вершину геометрического объекта. Он применяет преобразования модель → мир → камера → проекция.

// Пример вершинного шейдера в HLSL   v2f vert (appdata v) {       v2f o;       o.vertex = UnityObjectToClipPos(v.vertex); // Локальные → экранные координаты       o.uv = v.uv;       return o;   }  

В вершинной части конвейера часто пишутся деформации вроде анимации воды, колышущейся травы, «дыхания» объектов. И для вершинной анимации.

Хитрый трюк связанный с этой частью конвейера. В вебе и на мобилках, особенно старых, очень медленно работает Skinning, поэтому через вершинные анимации можно оптимизировать это. В текстуру записывается смещение вершин, задаётся правило смещения в зависимости от цвета текстуры. И дальше можно последовательно проигрывать смещая вертексы в шейдере по кадрам приводя 3д модель к нужному состоянию. Работает в разы быстрее скининга на старых устройствах.

Shader "Custom/VertexAnimationTexture" {     Properties {         _MainTex ("Albedo", 2D) = "white" {}         _AnimTex ("Animation Texture", 2D) = "black" {} // R32G32B32A32_Float         _AnimLength ("Animation Length", Float) = 1.0     }     SubShader {         Pass {             CGPROGRAM             #pragma vertex vert             #pragma fragment frag             #include "UnityCG.cginc"              struct appdata {                 float4 vertex : POSITION;                 float2 uv : TEXCOORD0;                 uint vertexId : SV_VertexID;             };              struct v2f {                 float2 uv : TEXCOORD0;                 float4 pos : SV_POSITION;             };              sampler2D _MainTex, _AnimTex;             float _AnimLength;              v2f vert (appdata v) {                 v2f o;                                  // Вычисляем текущий кадр анимации                 float time = frac(_Time.y / _AnimLength); // Зацикленная анимация                 float2 animUV = float2(                     (float)v.vertexId / (float)_AnimTex_TexelSize.z, // X = vertexId                     time                                              // Y = время                 );                                  // Читаем позицию из текстуры                 float3 animPos = tex2Dlod(_AnimTex, float4(animUV, 0, 0)).xyz;                                  // Применяем новую позицию                 o.pos = UnityObjectToClipPos(float4(animPos, 1));                 o.uv = v.uv;                 return o;             }              fixed4 frag (v2f i) : SV_Target {                 return tex2D(_MainTex, i.uv);             }             ENDCG         }     } }

Геометрический шейдер 

Геометрический шейдер (Geometry Shader) — это этап графического конвейера, который работает между вершинным и фрагментным шейдерами и позволяет динамически создавать, изменять или удалять геометрические примитивы (точки, линии, треугольники).

[maxvertexcount(3)] // Максимум 3 вершины на выходе (треугольник)   void geom(triangle v2f input[3], inout TriangleStream<v2f> stream) {       v2f output;       for (int i = 0; i < 3; i++) {           output.vertex = input[i].vertex + float4(0, 0.5, 0, 0); // Сдвигаем вершины вверх           output.uv = input[i].uv;           stream.Append(output);       }       stream.RestartStrip();   }  

Важно: В URP/HDRP геометрические шейдеры поддерживаются, но требуют аккуратной настройки.

Применяется для генерации травы и листвы из точек, для визуализации дебаг параметров (например нормали моделей), разбиения меша на части (эффекты разрушаемости).

Клиппинг

Клиппинг (Clipping) — это процесс отсечения частей графических примитивов (треугольников, линий, точек), которые находятся вне области видимости (например, за границами экрана или вне заданного пространства).

Как работает в конвейере?

  1. View Frustum Culling:

    • Unity автоматически отбрасывает объекты вне пирамиды видимости камеры.

  2. Clipping Space:

    • Вершины преобразуются в Clip Space (координаты от [-1, 1]).

    • Если вершина вне этого диапазона — она отсекается.

  3. Backface Culling:

    • Треугольники, повёрнутые «спиной» к камере, не растеризуются.

Клиппинг — ключевой этап рендеринга, который отбрасывает невидимые данные для оптимизации. В современных движках (Unity, Unreal) многие виды клиппинга настраиваются автоматически.

Растеризация

Растеризация (Rasterization) — это процесс преобразования векторных графических примитивов (треугольников, линий, точек) в растровое изображение (пиксели) для отображения на экране.

Как работает растеризация?

  1. На вход подаются примитивы (обычно треугольники после преобразования в экранные координаты).

  2. Определяются пиксели, покрываемые примитивом (с учётом их глубины, формы и размера).

  3. Для каждого пикселя генерируется фрагмент (данные для фрагментного шейдера: цвет, глубина, текстурные координаты и др.).

Растеризация — это мост между математическим описанием сцены и её пиксельным представлением. Современные GPU используют сложные алгоритмы для её ускорения, но разработчикам важно учитывать разрешение, плотность полигонов и настройки антиалиасинга для баланса между качеством и производительностью.

Фрагментный шейдер

Фрагментный шейдер (Fragment Shader) — это программа, выполняемая на GPU для каждого потенциального пикселя (фрагмента) примитива (треугольника, линии, точки) в процессе растеризации. Он определяет окончательный цвет, прозрачность и другие свойства пикселя перед записью в буфер кадра.

// Пример фрагментного шейдера в HLSL   fixed4 frag (v2f i) : SV_Target {       fixed4 col = tex2D(_MainTex, i.uv); // Чтение текстуры       return col * _Color;   }  

Применяется для реалистичного текстурирования, расчёта освещения (включая PBR-рендеринг), создания визуальных эффектов (например, dissolve, параллакс-маппинг), стилизации графики (toon-шейдинг, пиксель-арт), постобработки (bloom, размытие) и управления прозрачностью (альфа-обрезание, полупрозрачность). Оптимизированная работа фрагментного шейдера критична для производительности, особенно при сложных материалах или на мобильных устройствах.

Тесты и смешивание

Тесты — это этапы проверки фрагментов перед отрисовкой, включающие:

  • Тест глубины (Z-test) – отсеивание невидимых пикселей (если gl_FragDepth не проходит сравнение с Z-буфером).

  • Тест шаблона (Stencil test) – маскирование областей рендера (например, для зеркал или сложных UI).

  • Альфа-тест – отбраковка прозрачных фрагментов (через discard или clip()).

Смешивание (Blending) – комбинирование цвета нового фрагмента с уже отрендеренным в буфере кадра, управляется:

  • Формулами (например, SRC_ALPHA, ONE_MINUS_SRC_ALPHA для стандартной прозрачности).

  • Режимами (аддитивное, мультипликативное, наложение света).

Как это работает?

1. Тесты:

  • Глубина (Z-test): После фрагментного шейдера GPU сравнивает значение gl_FragDepth с содержимым Z-буфера. Если фрагмент «дальше» существующего значения — он отбрасывается.

  • Стенсил (Stencil test): Пиксель проверяется по шаблону в stencil-буфере (например, маска для портала). Если тест не пройден — фрагмент не рисуется.

  • Альфа-отсечение (Alpha test): Фрагменты с альфа-каналом ниже порога (например, if (alpha &lt; 0.5) discard) удаляются до смешивания.

2. Смешивание (Blending):

  • Формула: Цвет фрагмента (src) комбинируется с цветом в буфере кадра (dst) по правилу:

    final_color = src.rgb * src.a + dst.rgb * (1 - src.a)  
  • Режимы: Аддитивный (src + dst), умножение (src * dst), наложение света (для эффектов bloom).

3. Порядок операций:

  1. Фрагментный шейдер вычисляет цвет.

  2. Применяются тесты (stencil → depth → alpha).

  3. Если фрагмент «выжил» — выполняется смешивание с буфером.

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

  • Ранний тест глубины (Early-Z) пропускает ненужные фрагменты до шейдера.

  • Для непрозрачных объектов отключайте blending командой Blend Off.

Пример (HLSL в Unity):

Blend SrcAlpha OneMinusSrcAlpha // Стандартная прозрачность   ZWrite Off                    // Отключает запись глубины для полупрозрачных  

Этот механизм обеспечивает корректное наложение объектов и эффектов с контролем производительности.

URP vs HDRP vs Built-in: ключевые отличия

Built-in — пайплайн с базовым Forward/Deferred рендерингом, без оптимизаций под современные GPU. URP — оптимизированное решение для мобильных и ПК среднего уровня: Forward+, SRP Batcher, но без сложных эффектов. HDRP — AAA-рендеринг с Deferred, Ray Tracing и физически точным освещением, но требует мощного железа. Выбор зависит от платформы: URP для мобилок и инди-проектов, HDRP — для фотореализма, а Built-in лучше заменить на URP. Главный плюс Built-in в большой базе реализованных ассетов, который со временем нивелируется.

Заключение

Графический конвейер — это основа рендеринга в реальном времени. Понимание его работы помогает:

  • Писать эффективные шейдеры.

  • Оптимизировать производительность.

  • Выбирать правильный рендер-пайплайн в Unity.

Если вам интересны новости Unity разработки и в целом тема Unity — подписывайтесь на мой блог в телеграм. Я публикую там интересные новости и обзоры на них, свои мысли про бизнес, про фриланс и про разработку. Постараюсь до конца лета дописать серию статей целиком. Плюс лучший показатель того, что надо тема интересна — надо писать. Спасибо за внимание!


ссылка на оригинал статьи https://habr.com/ru/articles/927304/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *