UE4/Unity отрисовывает основные элементы карты — линии (Часть 2)

разработка игр

UE4/Unity отрисовывает основные элементы карты — линии (Часть 1)

предисловие

В первой части описывается основной процесс рисования линий, а во второй части в основном описываются проблемы с производительностью и эффектами, возникающие при рисовании линий. Когда вы закончите рисовать линию и захотите стилизовать ее штрихом, вы столкнетесь с неизбежной проблемой мерцания. При отрисовке большого количества пересекающихся дорог необходимо учитывать как производительность отрисовки, так и проблемы мерцания. В этой статье обобщаются методы эффективного рисования штриховых линий и излагаются исследованные решения для решения проблемы мерцания Z-Fighting.

Оптимизация производительности для пиксельного рендеринга с закругленными углами

В прошлой статье был представлен метод попиксельной отбраковки для создания скругленных углов В общем, для достижения цели динамического скругления математические вычисления в исходном ЦП были перенесены во фрагментный шейдер. Это, хотя и дает самые плавные результаты, также оказывает давление на графический процессор. Взяв в качестве примера закругленный код, на который влияет метод обработки графического процессора, инструкции if/else динамической ветви должны быть полностью выполнены, а инструкция отбрасывания также повлияет на раннюю Z-оптимизацию графического процессора, обе из которых будут иметь влияние на производительность.

fixed4 frag (v2f i) : SV_Target
{
    if(i.geometryInfo.x < 0)  // 起点侧线帽
    {    
        if(dot(float2(i.geometryInfo.x, i.geometryInfo.y), float2(i.geometryInfo.x, i.geometryInfo.y)) > 1)
        {   
            discard; // 距离圆心距离大于1则剔除
        }
    } 
    else if(i.geometryInfo.x > 1) // 终点侧线帽
    {
        if(dot(float2(i.geometryInfo.x - 1, i.geometryInfo.y), float2(i.geometryInfo.x - 1, i.geometryInfo.y)) > 1)
        {   
            discard; 
        }
    }

    return i.color;
 }

Поэтому оптимизация производительности инструкций во фрагментном шейдере в основном заключается в изменении его логики на линейную, удалении динамических ветвей и замене сброса на альфа-смешивание. Основным инструментом для упрощения процесса является стандартная функция CG step/clamp/lerp, которая определяется следующим образом, и гибкое использование этих функций позволяет избежать динамического ветвления.

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

fixed4 frag (v2f i) : SV_Target
{
    fixed4 clearColor = 0;
    fixed  isClear = 0;
	
    fixed origin = clamp(i.geometryInfo.z, 0 ,1);  // 两侧线帽x值收缩到0和1
    fixed4 isCap = step(0, origin * (origin - 1)); // 构建二值函数,线帽为1,线段为0
    fixed2 dist = fixed2(i.geometryInfo.z - origin, i.geometryInfo.w); // 构建距离向量
    isClear = step(1, dot(dist, dist)) * isCap; // 距离小于1(不需要剔除)为0,距离大于等于1(需要剔除)且是线帽像素,则为1
	
    return lerp(i.color, clearColor, isClear);
 }

Нарисуйте штрих линии

После завершения рисования линии в соответствии с предыдущей статьей, чтобы линию было легко наблюдать, обычно необходимо сделать линию штриховой. На самом деле линии, показанные в предыдущей статье, уже заштрихованы для эстетики, но требуется дополнительная прорисовка, чтобы сделать заштрихованную часть линии.

Чтобы уменьшить увеличение количества вершин и упростить расчет триангуляции, идентичный расширенный рисунок обычно выполняется с использованием ширины линии штриха под нарисованной линией заливки.Построение ширины линии штриха создает большую поверхность, делая поверхность состоит из двух линий.Наложение отображения может добиться эффекта штрихов линии. Ширина штриха этой схемы равна (sideLineWidth - lineWidth)/2.

Основной принцип заштрихованной линии описан выше, и в реальном рисовании логика рендеринга может быть оптимизирована в соответствии с характеристиками заштрихованной линии и заштрихованной линии. На практике в основном проводятся следующие исследования:

1. Извлеките точки изменения

Видно, что направление расширения линии штриха и закрашенной линии одинаково при рисовании, разница в том, что ширина линии отличается в зависимости от вектора расширения. Поэтому вычисление расширенных вершин может быть извлечено в вершинный шейдер и выполняться параллельно, при обработке данных вычисляется только расширенный опорный вектор, который и информация о ширине линии передаются в шейдер вместе со структурой uv, так что две части линии могут быть сложными.Визуализировать с одним и тем же шейдером. Но две части линии по-прежнему нужно рисовать дважды, что требует двух вызовов отрисовки.

2. Данные улучшены до вызова Draw Call.

Основываясь на мышлении вершинного шейдера, для рисования двух линий различаются только положение вершины и цвет, поэтому можно смоделировать пакетную операцию, а данные сетки двух линий можно объединить для рисования в одном Draw. Звонок звонок. Видно, что в процессе слияния двух мешей требуется корректировка только индекса треугольника в соответствии с количеством вершин, а остальные данные могут быть объединены напрямую.

public LineMesh CombineLineMesh(LineMesh appendMesh)
{
    int index = this.vertices.Count;
    for (int i = 0; i < appendMesh.triangles.Count; ++i)
    {
        appendMesh.triangles[i] += index;
    }

    this.triangles.AddRange(appendMesh.triangles);
    this.vertices.AddRange(appendMesh.vertices);
    this.color32s.AddRange(appendMesh.color32s);
    this.geometrys.AddRange(appendMesh.geometrys);
    this.parameters.AddRange(appendMesh.parameters);

    return this;
}

3. Метод рисования улучшен до вызова Draw Call.

Хотя для рендеринга в Exploration 2 был достигнут вызов Draw Call, линии штриха и заливки визуализируются с использованием двух наборов вершин. вершин, рисует сразу всю линию на основе информации о соотношении ширины штриха и ширины линии заливки. Этот подход требует использования информации о геометрии, представленной в предыдущей статье, для рисования закругленных углов.Информация x может идентифицировать длину, а значение y может использоваться в качестве идентификатора в направлении ширины. Если соотношение определено как отношение ширины линии, цвет рендеринга можно определить по распределению значений y во фрагментном шейдере.

ratio = lineWidth / sideLineWidth
abs(y)∈[0,ratio] -> color
abs(y)∈(ratio,1] -> sideColor

Эта схема может рисовать обведенную линию, используя только один набор вершин, но есть некоторые проблемы:

1. Логика рисования, подобная концентрическим окружностям, требуется для поддержки закругленных углов заглавных букв и углов линий, и необходимо ввести дополнительные условные суждения, которые повлияют на сложность логики и производительность.

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

Подводя итог, можно сказать, что улучшение метода рисования имеет свои ограничения, и метод рисования Исследования 2 больше подходит.

Исправить мерцающую проблему с Z-боем

После того, как схема рисования определена, следующая проблема, возникающая при рисовании, — это проблема Z-борьбы линии, то есть линия продолжает мерцать во время наблюдения. Причина в том, что мировые координаты, в которых находится перекрывающаяся часть линии штриха и линии заливки, точно совпадают.После того, как на преобразование координат влияет точность буфера глубины, фрагменты беспорядочно проходят определение глубины во время рендеринга, что в конечном итоге проявляется как мерцающая проблема на поверхности.

Проблема Z-борьбы является последним препятствием для рисования линий, что требует большого количества базовых знаний о графике. В процессе изучения решений я также лучше понимаю весь процесс рендеринга. Изученные решения резюмируются следующим образом:

1. Настройте мировые координаты вершин

Первым шагом в решении проблемы Z-fighting является обнаружение объектов с конфликтующими значениями глубины. В сцене отрисовки линии с обводкой причина мерцания заключается в том, что перекрывающаяся часть обведенной линии и закрашенной линии имеет одинаковое значение высоты мировых координат, в результате получается одинаковое значение глубины фрагмента после преобразования координат . Таким образом, вы можете добавить небольшое смещение к значению высоты конфликтующего лица и изменить локальные координаты, чтобы повлиять на преобразованное значение глубины, и, наконец, вы увидите, что явление мерцания исчезает.

Согласно предыдущему обсуждению, операция изменения локальных координат может выполняться параллельно в шейдере.Взяв Unity в качестве примера, установив переменную приоритета для точной настройки смещения вершины в направлении y, отображение приоритет контролируется.

fillLineMesh.priority = 1;

v2f vert (a2v v)
{
    v2f o;
    float4 pos = v.vertex + float4(v.parameter.x, 0, v.parameter.y, 0) * v.parameter.z; // 根据向量和线宽计算实际顶点位置
    pos += float4(0, priority / 100, 0, 0); // 顶点y方向进行微调,需要把握微调大小
    
    o.pos = UnityObjectToClipPos(pos); 
    o.color = v.color;
    o.geometry = v.geometry;

    return o;
}

Это временно решит проблему мерцания, но оно все еще будет возникать после уменьшения положения камеры. Причина в том, что точность буфера глубины ограничена, поэтому чем дальше расстояние от камеры, тем больше смещение, а точное смещение необходимо динамически регулировать в соответствии с расстоянием между вершиной и камерой. . На практике направление линии визирования и направление тонкой настройки вершины в большинстве случаев не совпадают, и при решении Z-боев с большим количеством перекрывающихся линий накопление большого количества смещений может визуально наблюдать, что линии не компланарны и не компланарны со всеми линиями.Методы отображения карты на одной плоскости непоследовательны, поэтому схема 1 обычно используется только как инструмент для первоначальной проверки причины Z-борьбы.

2. Используйте команду «Смещение».

Unity ShaderLab предоставляет команду Offset для точной настройки смещения.Определение команды и формула расчета следующие:

Offset Factor, Units
offset = m * factor + r * units

Где m — максимальное значение наклона глубины полигона, рассчитанное системой, чем более параллелен полигон ближней плоскости отсечения, тем ближе m к 0, а r — наименьшая единица разрешения значения глубины, которая является константой. определяется системой. Если многоугольник параллелен плоскости отсечения, для управления смещением можно использовать комбинацию коэффициента = 0 и единиц измерения = 1. Для многоугольников с включенным углом с плоскостью отсечения вам нужен фактор для совместного управления смещением. результат больше 0 сделает многоугольник смещенным от ближней плоскости отсечения, и конкретные значения параметров необходимо нащупать и подтвердить на практике.

Использование команды «Смещение» для воздействия на значение глубины пространства отсечения может решить проблему Z-борьбы между несколькими объектами, но ее нельзя использовать, когда все линии объединены в одну сетку, чтобы уменьшить вызов отрисовки, поэтому ее необходимо управляться вручную с помощью его принципа информации о глубине для разных линий в одной и той же сетке.

3. Настройте координаты отсечения вершин.

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

Перед растеризацией координаты претерпят преобразование модели-вида-проекции из локальных координат в координаты отсечения.Однородные координаты пространства отсечения, полученные из пространства наблюдения посредством преобразования матрицы проекции, затем преобразуются в координаты NDC z, полученные в пространстве экрана. Значение получено из z/w однородной координаты, которая определяет значение глубины. Для преобразования координат пространства обзора в координаты отсечения требуются следующие параметры:

f: дальняя плоскость отсечения

n: рядом с секущей плоскостью

поле зрения: перспектива

аспект: соотношение сторон камеры

Пусть координаты пространства наблюдения,

Тогда преобразование в координаты пространства отсечения:

В соответствии с правилом значения глубины добавление смещения -z*offset к значению z координаты обрезки может точно настроить глубину назад на величину смещения. В материале UE4 эффект смещения также может быть достигнут путем настройки смещения глубины пикселя.

v2f vert (a2v v)
{
    v2f o;
    o.pos = float4(UnityObjectToViewPos(float3(v.vertex.xyz)), 1.0);
    float z = o.pos.z;
    o.pos = mul(UNITY_MATRIX_P,  o.pos);
    o.pos.z = o.pos.z - z * v.parameter.z/1E8;// 使用parameter.z存储顶点偏移信息

    return o;
}

4. Отрегулируйте определение глубины

Все вышеперечисленные решения решают проблему Z-борьбы, создавая небольшие смещения между разными гранями, в то время как другая идея состоит в том, чтобы не увеличивать смещение, а задавая правила ограничения во время рендеринга, грани, нарисованные первыми, рисуются позже. наконец показывает правильное изображение. Этот подход требует сначала понимания концепции определения глубины.

Определение глубины выполняется после фрагментного шейдера.Каждый фрагмент несет свое значение глубины и сравнивает его со значением глубины в буфере глубины.Если определение проходит успешно, значение в буфере глубины будет установлено на значение глубины. Если обнаружение не удается, фрагмент отбрасывается. Unity ShaderLab использует две команды ZWrite и ZTest для управления этим процессом:

  • Записывать ли глубину фрагмента в буфер глубины после прохождения контрольного теста ZWrite, по умолчанию включено (ZWrite On)
  • ZTest определяет правила прохождения определения глубины значением глубины.По умолчанию определение глубины проходит, когда значение глубины фрагмента меньше или равно значению глубины в буфере глубины (ZTest LEqual).

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

ZWrite On
ZTest Always

резюме

Для проблемы мерцания ядром первых трех схем исследования является построение небольших смещений.Если количество лиц для боя слишком велико, будет наложено большое количество малых смещений для получения количественных изменений, которые могут повлиять на отображение перспективы. размер графики.В этом случае используется рекомендуемая схема.Четыре. В случае нескольких объектов его можно использовать вместе со вторым и четвертым решениями, и эффект будет лучше.

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

Автор: Программист Ах Ту

Ссылка на сайт:zhuanlan.zhihu.com/p/266042561

Источник: Чжиху

Авторские права принадлежат автору. Для коммерческих перепечаток, пожалуйста, свяжитесь с автором для получения разрешения, а для некоммерческих перепечаток, пожалуйста, укажите источник.