Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine)

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #1

К сожалению на ютубе и на форумах нет хороших туториалов по этой теме. Как-то что-то там чё-то срезается, и норм. А мне нужно максимально точно и понятно.

Для моих задач в продакшен нужно только вращение волюма по Yaw, так что если нужно по всем 3м осям - пишите в комментах.

В двух словах про механику: На уровне есть какая-то геометрия, и на всей этой геометрии установлен один материал (для увеличения производительности и снижения DrawCall). Один прямоугольный участок мне нужно срезать так, чтобы не зацепить другую геометрию. Над геометрией для срезания помещается Блюпринт Актор внутри которого есть BoxCollisionVolume. Мы опускаем этот блюпринт, и позицию+поворот+размеры этого бокса отправляем в Material Parameter Collection (MPC). Во всех материалах геометрии учитываются эти параметры из MPC.

Material Parameter Collection такой:

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #2

Intensity (float1) - Интенсивность этого эффекта

Rotation (float1) - Поворот по Z (Yaw)

Location (float4) - Позиция бокса

BoundingBox (float4) - Размеры бокса в абсолютных величинах

RotationAxis (float4) - Ось поворота бокса (на будущее). Для текущего туториала достаточно вектора (0, 0, 1). Но в будущем сделаю полный поворот в 3д (наверное).

Шейдер CutGeometry_Volume (материал инстанц) на кубиках лежит вот такой:

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #3

И сам Блюпринт Актор (например, Cut_BP) который служит объёмом для срезания:

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #4

Конечно же в этом Акторе выключены все Тики, и у BoxCollisionVolume выключена абсолютно вся коллизия:

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #5

В этом Блюпринте есть 2 внешние переменные, которые можно крутить после помещения на уровень: Intensity и Bounds.

Интенсивность (Intensity) и Абсолютная позиция бокса (Box→WorldLocation) - просто передаётся в MPC.

Параметром Bounds мы изменяем размеры нашего BoxCollisionVolume, чтобы на уровне видно было что мы меняем:

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #6

BoxExtents для 3д-сцены и для шейдера к сожалению разные. Мы видим одно, а шейдер видит другое - он видит HalfExtents, так что я домножаю наши размеры на 2 и передаю в MPC.

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #7

Half Extends - это Объём, который можно описать вокруг геометрии меша.

По -X и +X выбираются ближайшие к пивоту меша точки, и берётся максимальная. Получается максимальная граница по X. Так же для Y. И так же для Z. В итоге получился объём, который принято делить на 2 и называть это HalfExtends =) Так во многих игровых движках: хочешь получить BoundingBox и никогда не знаешь что в итоге получишь: полный размер или половинный.

Дальше вращение по Z (Yaw):

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #8

Здесь я беру абсолютное вращение компонента Box, не смотря на то, какой относительный поворот компонента и отправляю в MPC. World Rotation точно даёт нужное вращение.

-180 и +180 - это возможный поворот в градусах на 3д сцене на уровне. Крутим Актора влево-вправо и получаем такие числа. Но шейдеру нужны не эти градусы, а относительное приведённое от 0..360 к 0..1 amount-число. MapRangeClamped привела какой-то угол в относительное значение.

Было -180 .. +180, стало -0.5 .. +0.5

Последний шаг - отправляю Ось вращения в шейдер:

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #9

Что теперь делает шейдер, когда получил все эти значения из MPC?

Для начала преобразовываем float4 в float3. RotationAxis в MPC лежит «цветом» это структура флоатов (R, G, B, A). Нода ComponentMask (которая на скриншоте называется Mask(RGB)) может убрать лишний для нас компонент A, и на выходе мы получили (R, G, B).

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #10

Передали в ноду RotateAboutAxis: ось вращения, угол вращения (от -0.5 до +0.5), точку вращения Pivot, и абсолютную позицию пикселя объекта в 3д. Мы вращаем один пиксель в этом пиксельном шейдере, который в Анриале называется Материал.

Ту позицию в 3д, которая получилась после поворота мы складываем абсолютной позицией и наконец-то получаем точку <A> для BoxMask. Второй точкой <B> будет позиция нашего BoxCollisionVolume из Актора.

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #11

BoxMask в пиксельном шейдере: между 2мя точками в 3д мире строится вектор направления, который не должен выйти за BoundingBox, в результате чего получается 3д-маска.

MaskEdgeFalloff - это на сколько далеко от границы будет градиентом расползаться маска. В моём примере от границы на дистанцию 5 пикселей будет малюсенький градиент.

При скалировании Актора шейдер перестаёт адекватно воспринимать границы, так что лучше Scale оставлять равным единице. Тогда всё будет пиксель в пиксель.

Срезаем геометрию волюмом пиксель в пиксель (Unreal Engine), image #12
40 views·3 shares