SkorAU

SkorAU

На Пикабу
439 рейтинг 37 подписчиков 79 подписок 4 поста 3 в горячем
Награды:
Пикабу 17 лет!5 лет на Пикабу
13

Материал, имитирующий прибой [UE4]⁠⁠

Добрый день. Сегодня мы рассмотрим, как создать материал, имитирующий прибой. Для этого нам понадобится функция "DistanceToNearestSurface", функция "Noise" или какая-нибудь текстура с шумом и немного фантазии. В конце поста есть ссылка на видеоурок.


Подготовка проекта

Первым делом нужно включить опцию "Generate Mesh Distance Fields" в настройках проекта. Это необходимо для корректной работы функции "DistanceToNearestSurface".

Затем открываем "World Settings" и задаем необходимую нам дальность прорисовки "Global DistanceField View Distance".

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

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

Если на сцене не будет ничего кроме ландшафта и плоскости, то "DistanceField" может вести себя немного странно. Но после того как на сцене появится еще какой-нибудь объект, содержащий "StaticMesh", все придет в норму.


Создание материала

Давайте создадим материал и назовем его "M_Surf". "Blend Mode" устанавливаем "Translucent".

Блок "SurfArea" - отвечает за размеры области прибоя и передает в функцию "MF_Surf" градиент, который меняется от нуля (граница появления волн) до единицы (береговая линия).

Параметры:

"Distance" - позволяет регулировать расстояние от берега, до границы появления волн (область прибоя)
"BaseColor" - задает цвет поверхности воды
"FoamColor" - задает цвет пены (волн)
"Opacity" - задает прозрачность материала


Функция "MF_Surf"

Содержит всю основную логику. На вход подается градиент, а на выходе получается система волн.

Блок "Global Noise"

Отвечает за глобальное искажение волн.

Параметры:

"GlobalNoiseScale" - отвечает за масштаб искажений
"GlobalNoisePower" - отвечает за силу искажений

Переключатель "UseGlobalNoise" - позволяет включать и отключать блок "GlobalNoise"

Узел "Noise" - позволяет настроить искажения


Блок "Time"

Отвечает за движение волн. Складывается с блоком "GlobalNoise" и подается на вход функций "MF_ForwardWaves" и "MF_ReverseWave". Параметр "WaveSpeed" позволяет регулировать скорость волн.


Блок "LocalNoise"

Отвечает за локальное искажение волн (пену и мелкие неоднородности).

Параметры:

"LocalNoiseScale" - отвечает за масштаб искажений
"LocalNoiseValueOffset" - отвечает за силу искажений

Переключатель "UseLocalNoise" - позволяет включать и отключать блок "LocalNoise"

Узел "Texture Sample" - позволяет выбрать текстуру, которая будет использоваться для локальных искажений. Сейчас выбран Perlin_Noise из стартового набора (Texture2D'/Game/StarterContent/Textures/T_Perlin_Noise_M.T_Perlin_Noise_M')


Функция "MF_CosineGradient"

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

Функция "MF_ForwardWaves"

Создает волны, бегущие в сторону берега.

Блок "NumberOfWaves"

Параметр "NumberOfWaves" задает количество волн.

Узлы:

"1-x" - задает направление движения (волны бегут в сторону берега)

"Add" - добавляет глобальные искажения (блок "GlobalNoise") и изменение положения со временем (блок "Time")


Блок "ForwardWaves"

Вызывает функцию "MF_Waves" и задает параметры набегающих волн. Функция "MF_Waves" и ее входные параметры будут описаны ниже.

Переключатель "UseForwardWaves" - позволяет включать и отключать набегающие волны.


Функция "MF_ReverseWave"

Создает волну, бегущую от берега. Определяет размеры области, в которой эта волна распространяется, и вызывает функцию "MF_CoastLine", которая создает пену у самого берега.

Блок "ReverseWaveArea"

Определяет область в которой распространяется обратная волна. А также задает направление движения, добавляет глобальные искажения (блок "GlobalNoise") и изменение положения со временем (блок "Time").

Параметры:

"ReverseRelativeDistance" - определяет относительное расстояние (часть области прибоя), на которое будет распространяться обратная волна
"ReverseWaveTimeOffset" - позволяет изменять момент появления обратной волны. Если этот параметр равен нулю, то обратная волна появляется сразу после того, как набегающая волна ударилась о берег.
"ReverseWaveGradient" - определят, как быстро (на каком расстоянии от берега) исчезает обратная волна


Блок "ReverseWave"

Вызывает функцию "MF_Waves" и задает параметры обратной волны. Функция "MF_Waves" и ее входные параметры будут описаны ниже.

Переключатели:

"UseReverseWaves" - позволяет включать и отключать обратную волну
"UseCoastLine" - позволяет включать и отключать пену у берега.


Функция "MF_CoastLine"

Создает пену и движение воды у берега.

Блок "Time" отвечает за скорость изменения структуры пены рядом с берегом. Скорость регулируется параметром "CoastNoiseChangeSpeed".


Блок "CoastLineArea"

Определяет область образования пены и ее яркость.

Параметры:

"CoastLineGradient" - определят, как быстро (на каком расстоянии от берега) исчезает пена
"CoastPower" - определяет яркость пены


Блок "CoastNoise"

Отвечает за симуляцию пены.

Параметры:

"CoastNoiseScale" - определяет масштаб искажений
"CoastNoisePower" - определяет силу искажений (размытость границ)
"CoastLineValueOffset" - позволяет изменять контрастность

Блок "NoiseLoop" делает поведение пены периодическим.


Функция "MF_Waves"

Создает волны. Направление движения, количество волн и другие их характеристики определяются входными параметрами.

Входные параметры:

"WaveFrontRelativeWidth" - определяет, какую часть от волны занимает ее голова (передняя часть)
"WaveFrontPower" - определяет, насколько яркой будет передняя часть волны
"WaveBodyPower" - определяет, насколько яркой будет тело волны
"WaveFrontGradient" - определяет, насколько резко изменяется яркость передней части волны (четкость передней границы)
"WaveBodyGradient" - определяет, насколько резко изменяется яркость задней (основной) части волны


Блок "BaseWaves" превращает входной градиент (от "0" до n) в n градиентов от "0" до "1". Создает основу для волн. Если результат работы этого блока сразу передать на выход функции,

то получится следующая картина

Блок "Front"

Отвечает за переднюю часть волны (ее голову). Определяет ее размеры, яркость и размытость границ.

Блок "Body"

Отвечает за заднюю часть волны (ее тело). Определяет ее размеры, яркость и размытость границ.

Экземпляр материала (Material Instance)

Создаем экземпляр материала и применяем его к плоскости.

Для того чтобы был понятен масштаб, на сцену добавлены два персонажа.

Ниже приведены настройки, использованные в "Material Instance":

Заключение

А на сегодня это все. Спасибо за внимание.


Ссылка на видеоурок:

https://youtu.be/AoaOzQ2_hgI

Показать полностью 24
25

Создание простой области видимости (C++) [UE4]⁠⁠

Добрый день. Это второй мой пост на тему создания простой области видимости. И он тесно связан с первым постом, так как конечный результат будет точно таким же как и там. Только получим мы этот результат с помощью C++. Если какие-то детали разобраны недостаточно подробно, то в конце поста есть ссылка на видеоурок.


Создание проекта

За основу возьмем шаблон "ThirdPerson" со стартовым набором. Тип проекта выберем "С++" и назовем его "SimpleVisionAreaCPP" (это название влияет на названия C++ классов, которые создаются автоматически). В папке "Content" создаем рабочую папку "MyContent", а в ней папки "BP" и "Materials".


Создание материала

Переходим в папку "Materials". Создаем новый материал и называем его "M_VisionArea". 

Процесс создания этого материала подробно описан в первом посте.

Назначение параметров:

"ExternalRadius" - отвечает за дальность видимости (внешний радиус)

"InternalRadius" - отвечает за область около персонажа (внутренний радиус)

"Density" - отвечает за размытость границ

"HalfAngle" - отвечает за угол обзора (половинный угол)


В дальнейшем нам понадобится DecalMaterial, поэтому создадим копию нашего материала и назовем ее "M_DecalVisionArea". Откроем этот материал и изменим "Material Domain" с "Surface" на "Deferred Decal".


Настройка персонажа

Переходим в папку "C++ Classes"/"SimpleVisionAreaCPP", выбираем С++ класс "SimpleVisionAreaCPPCharacter", нажимаем на него правой кнопкой мыши и создаем производный C++ класс. И называем его "MyCharacter".

Переходим в папку "BP" и создаем новый "Blueprint Class" (в качестве родителя выбираем только что созданный C++ класс "MyCharacter"). Называем его "BP_MyCharacter" и помещаем на сцену.

Сейчас в "BP_MyCharacter" не выбраны "Skeletal Mesh" и "Anim Class" (анимация).

Создаем конструктор класса. Для этого в заголовочный файл "MyCharacter.h" добавляем строчки:

public:
AMyCharacter();
а в cpp файл "MyCharacter.cpp" добавляем два заголовочных файла:
#include "Components/SkeletalMeshComponent.h"
#include "UObject/ConstructorHelpers.h"
Создаем переменную CharacterMesh:
USkeletalMeshComponent* CharacterMesh = GetMesh();

И используем метод "GetMesh", для того чтобы получить "Mesh" (SkeletalMeshComponent) из "BP_MyCharacter" (выделен желтым на предыдущем рисунке). Ищем "SkeletalMesh" при помощи "ConstructorHelpers::FObjectFinder":

static ConstructorHelpers::FObjectFinder <USkeletalMesh> SkeletalMeshHelper(TEXT("SkeletalMesh'/Game/Mannequin/Character/Mesh/SK_Mannequin.SK_Mannequin'"));

Путь "SkeletalMesh'/Game/Mannequin/Character/Mesh/SK_Mannequin.SK_Mannequin'" можно получить, если выбрать интересующий нас объект в "Content Browser", щелкнуть по нему правой кнопкой мыши и выбрать "Copy Reference".

Если не знаете где расположен нужный объект, но знаете "Bluprint Class", где он используется, то можете перейти в этот класс и воспользоваться лупой.

После этого в "Content Browser" будет открыта папка с нужным объектом, а сам объект будет выделен.

Проверяем, удалось ли найти нужный объект. И если удалось, то применяем его:

if (SkeletalMeshHelper.Object) {
CharacterMesh->SetSkeletalMesh(SkeletalMeshHelper.Object);
}
Перемещаем и поворачиваем наш манекен так, чтобы ноги у него находились на уровне пола, а смотрел он в сторону оси "X":
CharacterMesh->SetRelativeLocation(FVector(0.0, 0.0, -97.0));
CharacterMesh->SetRelativeRotation(FRotator(0.0, 270.0, 0.0));

По аналогии со "SkeletalMesh"  добавляем анимацию:

static ConstructorHelpers::FClassFinder <UAnimInstance> AnimInstanceHelper(TEXT("AnimBlueprint'/Game/Mannequin/Animations/ThirdPerson_AnimBP.ThirdPerson_AnimBP_C'"));
if (AnimInstanceHelper.Class) {
CharacterMesh->SetAnimInstanceClass(AnimInstanceHelper.Class);
}
Только в этот раз мы ищем не объект, а класс. Поэтому к пути, который получается с помощью  "Copy Reference": нужно дописать "_C".

После сборки проекта у нас получается вот такой "BP_MyCharacter":

На этом настройка персонажа закончена. Давайте теперь добавим область видимости.


Добавление области видимости к персонажу

Добавляем в заголовочный файл две переменные:

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "VisionArea|Components")
UStaticMeshComponent* VisionArea_SMC;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "VisionArea|Components")
UMaterial* VisonArea_Material;
В "StaticMeshComponent" мы поместим плоскость, а переменная "VisonArea_Material" позволит выбрать материал, который будет использоваться для отображения области видимости.


В cpp файле создаем дефолтный "StaticMeshComponent" и называем его "VisionArea_SMC" (это имя будет отображаться редакторе).

VisionArea_SMC = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("VisionArea_SMC"));

Прикрепляем его к корневому компоненту, перемещаем на уровень пола и отключаем столкновения:

VisionArea_SMC->SetupAttachment(RootComponent);
VisionArea_SMC->SetRelativeLocation(FVector(0.0, 0.0, -95.0));
VisionArea_SMC->SetCollisionEnabled(ECollisionEnabled::NoCollision);
Ищем плоскость. И если удалось найти, то используем ее.
static ConstructorHelpers::FObjectFinder <UStaticMesh> PlaneHelper(TEXT("StaticMesh'/Engine/BasicShapes/Plane.Plane'"));
if (PlaneHelper.Object)
{
VisionArea_SMC->SetStaticMesh(PlaneHelper.Object);
}
Аналогично поступаем с материалом:
VisonArea_Material = CreateDefaultSubobject<UMaterial>(TEXT("VisonArea_Material"));
static ConstructorHelpers::FObjectFinder <UMaterial>MaterialHelper(TEXT("Material'/Game/MyContent/Materials/M_VisionArea.M_VisionArea'"));
if (MaterialHelper.Object)
{
VisonArea_Material = MaterialHelper.Object;
}

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

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VisionArea|Base")
float HalfAngle = 30.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VisionArea|Base")
float ExternalRadius = 1000.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VisionArea|Base")
float InternalRadius = 50.0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VisionArea|Base")
FLinearColor BaseColor = FLinearColor(0.0, 1.0, 0.0, 1.0);
Также нам понадобится переменная "VisionArea_DMI", отвечающая за динамический материал,
UMaterialInstanceDynamic* VisionArea_DMI;

и функция "OnConstruction", которая будет служить заменой функции "ConstructionScript" из Blueprint:

virtual void OnConstruction(const FTransform& Transform) override;

Переходим в cpp файл, создаем "MaterialInstanceDynamic", используя "VisonArea_Material", и связываем наши переменные с параметрами материала. Все это делаем внутри функции  "OnConstruction":

void AMyCharacter::OnConstruction(const FTransform& Transform) {
//----------------VisionArea_SMC-------------------//
float Scale = ExternalRadius / 50.0;
VisionArea_SMC->SetRelativeScale3D(FVector(Scale, Scale, 1.0));
VisionArea_DMI = UMaterialInstanceDynamic::Create(VisonArea_Material, nullptr);
VisionArea_DMI->SetVectorParameterValue(FName("BaseColor"), BaseColor);
VisionArea_DMI->SetScalarParameterValue(FName("HalfAngle"), HalfAngle);
VisionArea_DMI->SetScalarParameterValue(FName("InternalRadius"), 0.5 * InternalRadius / ExternalRadius);
VisionArea_SMC->SetMaterial(0, VisionArea_DMI);
}

"BP_MyCharacter" после сборки проекта:

Давайте теперь добавим возможность использовать "DecalMaterial". Для этого добавляем в заголовочный фал две переменные, отвечающие за "DecalComponent" и "DecalMaterial" :

UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "VisionArea|Components")
UDecalComponent* VisionArea_DC;
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "VisionArea|Components")
UMaterial* VisionArea_DecalMaterial;
а также переменную "UseDecal", которая позволить переключаться между "StaticMeshComponent" и "DecalComponent" (менять тип области):
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VisionArea|Base")
bool UseDecal;
Переходим в cpp файл и добавляем заголовочные файлы:
#include "Materials/MaterialInstanceDynamic.h"
#include "Components/DecalComponent.h"

В функцию "OnConstruction" (по аналогии с "VisionArea_SMC" ) добавляем следующий код:

//---------------VisionArea_Decal-------------------//
float DecalScale = ExternalRadius / 256.0;
VisionArea_DC->SetRelativeScale3D(FVector(10.0, DecalScale, DecalScale));
VisionArea_DMI = UMaterialInstanceDynamic::Create(VisionArea_DecalMaterial, nullptr);
VisionArea_DMI->SetVectorParameterValue(FName("BaseColor"), BaseColor);
VisionArea_DMI->SetScalarParameterValue(FName("HalfAngle"), HalfAngle);
VisionArea_DMI->SetScalarParameterValue(FName("InternalRadius"), 0.5 * InternalRadius / ExternalRadius);
VisionArea_DC->SetMaterial(0, VisionArea_DMI);
а также устанавливаем видимость для "VisionArea_SMC" и "VisionArea_DC":
VisionArea_SMC->SetVisibility(!UseDecal);
VisionArea_DC->SetVisibility(UseDecal);

Осталось только собрать проект.


Заключение

Хотел добавить итоговый вариант заголовочного и cpp файлов в заключение. Но добиться нормального форматирования текста на pikabu  у меня не получается, поэтому полный текст "MyCharacter.h" и "MyCharacter.cpp" я добавлю в комментарии. Может быть кому-то пригодится.


Ссылка на видеоурок:

https://youtu.be/i6vs5tC_uCQ

Показать полностью 8 1
58

Создание области видимости с затенением [UE4]⁠⁠

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

Если кому-то удобнее смотреть видео или какие-то детали разобраны недостаточно подробно, то в конце поста есть ссылка на видеоурок (состоит из двух частей).


Введение

В данном примере мы будем использовать процедурную сетку и трассировку линий. А для того чтобы избежать лишнего использования ресурсов и не создавать избыточные элементы сетки, напишем, основываясь на методе половинного деления, функцию, которая будет уточнять положение границ объектов. На картинке выше красным показаны базовые линии сетки (в данном примере их всего 4 на область видимости с углом 90 градусов), а синим — линии сетки, которые образовались после уточнения границ (их 10, так как для каждой границы создается две линии). И этого хватило чтобы правильно отрисовать тени.


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


Добавление трассировки линий к персонажу

Создаем новый проект. За основу берем "Blueprint" шаблон "ThirdPerson" со стартовым набором.

Переходим в папку Content/ThirdPersonBP/Blueprints и открываем Blueprint Class "ThirdPersonCharacter". Добавляем компонент "ProceduralMesh" и создаем переменные, которые понадобятся нам в дальнейшей работе.

Все переменные расположены в трех категориях:

- Base - параметры, определяющие размеры области видимости, ее расположение и детализацию;

- Auxiliary - переменные необходимые во время работы;

- Debug - параметры использующиеся при отладке проекта (не являются необходимыми, но сильно упрощаю жизнь).

Обратите внимание, что у переменных "Base" и "Debug" нужно включить "глазик", что позволит менять их в редакторе для каждого персонажа отдельно.


Base

HalfAngle - половинный угол обзора

DeltaAngle - желаемый угол между базовыми (красными) линиями сетки

ExternalRadius - дальность видимости

bUseExternalPrecise - включает или отключает использование функции "PreciseExternalBorders", отвечающей за точное определение внешних границ объектов

PreciseAngle - угол, задающий точность определения границ объектов, при условии, что "bUseExternalPrecise " = "true"

HeightOfCheck - высота на которой проверяется наличие препятствий

HeightOfShow - высота на которой будет отрисовываться область видимости, являющаяся результатом проверки на высоте "HeightOfCheck"


Auxiliary

NumberOfSections - Количество элементов базовой сетки (2*HalfAngle/DeltaAngle с округлением в большую сторону)

dAngle - реальный угол между базовыми (красными) линиями сетки (2*HalfAngle/NumberOfParts), может незначительно отличаться от "DeltaAngle"

StartPoint - координаты точки из которой стартуют лучи

HitResults - массив, состоящий из элементов типа "Hit Result", в которых содержатся результаты работы стандартной функции "LineTraceByChannel"


Debug

DrawDebugType - позволяет выбрать несколько режимов отрисовки лучей (тип "EDraw Debug Trace")

DebugDrawTime - длительность отрисовки лучей

bRebuild - позволяет перезапустить "ConstructionScript"

PreciseCount - позволяет ограничить количество итераций в функции "BisectionMethod"



ConstructionScript

Во вкладке "ConstructionScript" (картинка сверху) создаем три блока. Блок 1 позволяет перезапустить "ConstructionScript" для конкретного персонажа при изменении значения переменной "bRebuild" из редактора, а в блоках 2 и 3 вызываем функции "Start" и "LineTracing", которые приводятся ниже.


Функция "Start"

Используется для задания начальных параметров, зависящих от параметров из категории "Base"

Из переменных "HeightOfCheck" и "HeightOfShow" вычитается "100" для того, чтобы значения этих переменных равные "0" соответствовали уровню пола.


Функция "LineTracing"

Генерирует лучи в диапазоне углов от "-HalfAngle" до "HalfAngle", если считать от того направления куда смотрит персонаж (обозначим "ActorForwardVector"), с шагом по углу "dAngle" и записывает результаты трассировки в массив "HitResults".

Блок 1. Вычисляем угол между i-ым лучом и "ActorForwardVector".

(получить это направление можно с помощью функции "GetActorForwardVector")

Блок 2. Задаем направление i-ого луча и его длину.

Блок 3. Делаем трассировку i-ого луча с помощью функции "LineTraceByChannel" и добавляем результат в массив "HitResults". По умолчанию до столкновения с каким-либо препятствием луч отображается красным, а после столкновения — зеленым.

Результат работы цикла

Блок 4. После завершения цикла запускаем функцию "CreateProceduralMesh".


Функция "CreateProceduralMesh"

Создает сетку, опираясь на массив "HitResults", который был получен в функции "LineTracing" и уточнен в функции "PreciseExternalBorders", если выбрана соответствующая опция.

Блок 1. Проверяет нужно ли использовать функцию "PreciseExternalBorders" для уточнения границ.

Блоки 2 и 3. Проверяют встретил ли луч какое-нибудь препятствие. Если не встретил, то для создания треугольника берется конец луча, а если встретил, то координата точки взаимодействия. Рассматриваются i-ый и i+1-ый лучи. Результаты записываются в "L_LeftPoint" и "L_RightPoint" соответственно.

Блок 4. Используя встроенную функцию "CreateMeshSection", создает треугольник и применяет к нему материал "MI_Base" (показан ниже). Функция "CreateMeshSection" использует относительные координаты, поэтому мы вычитаем вектор "StartPoint" из координат всех трех точек и отправляем массив из этих точек на вход "Vertices". На вход "Triangles" подается массив, каждые три элемента которого указывают из каких трех точек предыдущего массива нужно составлять очередной треугольник. Стоит обратить внимание на направление обхода, так как от этого зависит направление нормали, а следовательно, увидим ли мы наш треугольник.

Блок 5. Является следствием того, что функция "CreateMeshSection" использует относительные координаты, и из-за этого "ProceduralMesh" поворачивается в два раза быстрее персонажа. Поэтому приходится доворачивать ее назад.


Материал "MI_Base"

Создаем материал "M_Base".

"BlendMode" устанавливаем на "Translucent" и добавляем параметр "BaseColor".

Создаем "Material Instance" под названием "MI_Base".


Сравнение результатов при разных значения "DeltaAngle" с уточнением границ и без уточнения

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

Хочу обратить внимание на то, что граница тени для области с уточнением нисколько не изменила своего положения во всех трех случаях и по-прежнему существенно точнее чем граница области без уточнения, так как "PreciseAngle" равен "0,01°". Отличия можно увидеть на картинках ниже.

Если персонаж не двигается, то такие неточности несильно заметны с "большого" расстояния, но во время движения граница начинает заметно дергаться.

Стоит отметить, что для DeltaAngle = 20° и выбранных параметрах области возможна ситуация, когда на большой дистанции ни один из базовых лучей (красные линии) не попадет в куб, и поэтому положение границ уточняться не будет в любом случае, а следовательно, и куб не будет отбрасывать тень. Учитывая размеры области и кубов, можно сказать, что DeltaAngle = 10° и использование функции "PreciseExternalBorders" гарантировано позволят захватить куб, так как расстояние между концами базовых лучей всегда будет меньше, чем размеры куба.


Функция "PreciseExternalBorders"

Определяет соседние лучи, попадающие в разные объекты (также подходит вариант, когда один луч попадает в объект, а второй нет), и вызывает для них функцию "BisectionMethod", которая уточняет положение границ объектов методом половинного деления.

Блок 1. Добавляет i-ый элемент из массива "HitResults" в локальный массив "L_HitResults"

Блок 2. Определяет попадают ли i-ый и i+1-ый лучи в разные объекты.

Блок 3. Определяет точку, которая пойдет в функцию "BisectionMethod". Если луч попадает в объект, то берется координата точки взаимодействия, а если не попадает, то координата конца.

Блок 4. Вызывает функцию "BisectionMethod". Результаты ее работы (дополнительные лучи, определяющие уточненное положение границ) добавляются в массив "L_HitResults".

Блок 5. Обновляем массив "HitResults".


Функция "BisectionMethod"

Уточняет положение границ объектов методом половинного деления.

Обозначения поясняются чуть ниже при описании локальных переменных.

Локальные переменные:

"L_LeftDirection" - направление левого луча (L)

"L_RightDirection" - направление правого луча (R)

"L_StartPoint" - координаты точки из которой выходят лучи

"L_MidDirection" - биссектриса угла между правым и левым лучом (M)

"L_LeftActor" - объект, в который попал левый луч

"L_RightActor" - объект, в который попал правый луч

"L_CurrentAlpha" - текущий угол между правым и левым лучом (A)

"L_NewHits" - массив, в который добавляются результаты "попаданий" левого и правого луча, после того как угол между ними становится меньше, чем "PreciseAngle".

"L_OtherActors" - массив из объектов, которые оказались между объектами "L_LeftActor" и "L_RightActor"

Блок 1. Инициализирует необходимые локальные переменные

Блок 2. Сравнивает угол между левым и правым лучом с "PreciseAngle" и проверяет сколько прошло итераций

Блок 3. Определяет угол между левым и правым лучом

Блок 4. Определяет биссектрису угла между левым и правым лучом

Блок 5. Проводит трассировку луча, направленного по биссектрисе

Блок 6. Выбирает какой из лучей (левый или правый) нужно заменить на биссектрису и проверяет наличие других объектов между лучами

Блок 7. Определяет куда попадают лучи после того, как была достигнута нужная точность, и записывает результаты в массив "L_NewHits"

Блок 8. Проверяет остались ли еще дополнительные объекты между лучами, и если остались, то запускает девятый блок. Если же таких объектов нет, то функция завершает свою работу и возвращает список найденных границ ("L_NewHits").

Блок 9. Берет первый объект из дополнительных и запускает уточнение его границ (цикл while).


Схематичный пример наличия дополнительного объекта между лучами.

Сначала была найдена граница между пустотой и шаром (I), а потом — между шаром и треугольником (II).


Реальный пример. На левой картинке блоки 8 и 9 отключены, а на правой работают.

В данном случае между левой стеной и "углом" оказалась область с максимальной дальностью (ее видно на правой картинке в красном круге), что и привело к нежелательному результату на левой картинке. Конечно, в этом примере угол между базовыми лучами равнялся 30° и в такую область может попасть далеко не один дополнительный объект. Если же мы уменьшим угол между лучами, то шансы на наличие дополнительного объекта сильно упадут, хотя и останутся не нулевыми. Поэтому мы и делаем дополнительную проверку (блок 8).


Последние приготовления

Создаем функцию "Update", в которой обновляем переменную "StartPoint" и удаляем старые данные из "HitResults" и "ProceduralMesh".

Переходим во вкладку "EventGraph" и соединяем "EventTick" c функцией "Update" и "LineTracing".

Теперь область видимости готова.


Заключение

Еле-еле уложился в отведенные 25 картинок. В будущем придется либо разбивать такие посты на пару частей, либо не вдаваться в излишние детали. Я больше склоняюсь ко второму варианту, так что если детали все-таки нужны, то напишите в комментариях.


В планах добавить уточнение контуров вогнутых тел и внести некоторые изменения в уточнение границ. Есть мысли по поводу того, чтобы разные части области видимости имели разные цвета, в зависимости от высоты объекта, который еще можно увидеть в данной точке. Также хочу постараться для все своих постов по "Blueprint"-ам делать второй вариант на C++.



Ссылки на видеоурок:

Создание области видимости с затенением. Часть 1.

Создание области видимости с затенением. Часть 2.

Показать полностью 22 2
243

Создание простой области видимости для AI или персонажа [UE4]⁠⁠

Добрый день! Меня зовут Александр. В этом посте я хотел бы рассказать о том, как в Unreal Engine 4 создать простую область видимости для AI или персонажа. Пример такой области Вы можете видеть на картинке выше. Если кому-то удобнее смотреть видео или какие-то детали разобраны недостаточно подробно, то в конце поста есть ссылка на видеоурок.


Создание проекта

За основу возьмем "Blueprint" шаблон ThirdPerson со стартовым набором.

Создание материала

Создаем базовый материал и называем его "M_VisionArea". Параметр "Blend Mode" устанавливаем на "Translucent" и создаем параметр "BaseColor".

Затем используем узел "RadialGradientExponential" и создаем параметры "ExternalRadius" и "Density", которые будут отвечать за внешний радиус и размытость границ нашей области видимости. Устанавливаем значение "ExternalRadius" равным "0,5", а значение "Density" равным "1000".

В результате у нас получается белый круг, вписанный в черный квадрат (белый цвет соответствует "1", а черный "0") Это именно то, что нам нужно, так как после мы будем применять наш материал к квадратной плоскости.


Для того чтобы у нас была возможность регулировать внутренний радиус области видимости мы копируем узлы представленные на предыдущей картинке, меняем параметр "ExternalRadius" на "InternalRadius", устанавливаем его значение равным "0,05" (это значение не играет особой роли, так как  в дальнейшем мы будем его задавать в настройках персонажа) и добавляем узел "1-x".

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

Теперь создаем узел "VectorToRadialValue" и используем выход "VectorConvertedToAngle"

В данном случае градиент меняется в зависимости от угла.


Для того чтобы вырезать интересующий нас угол, мы воспользуемся узлом "if" и создадим параметр "HalfAngle", отвечающий за половинный угол обзора.  Устанавливаем значение "HalfAngle" равным "0,25", а значения входов "A>B" и "A<B" равными "0" и "1" соответственно.

Из картинки видно, что значению "HalfAngle" равному "0,25" соответствует четверть круга.


Поскольку мне удобнее работать с градусами, а "HalfAngle" - это половинный угол, то устанавливаем значение "HalfAngle" равным "45" градусам (это значение может быть любым) и добавляем узел "Divide", где знаменатель равен "180".

Теперь полный угол (светлая область) равняется "90" градусам (2*"HalfAngle"), как и должно быть.


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

Перемножаем наши два выражения ("Ring" и "Angle"). Для того чтобы у нас была возможность менять прозрачность материала перемножаем получившийся результат с выходом параметра "BaseColor", отвечающим за прозрачность, и соединяем со входом "Opacity".

В дальнейшем нам понадобится DecalMaterial, поэтому создадим копию нашего материала и назовем ее M_DecalVisionArea.  Откроем этот материал и изменим "Material Domain" с "Surface" на "Deferred Decal".

На этом с материалами все и мы переходим к персонажу.


Добавление области видимости к персонажу

Переходим в папку Content/ThirdPersonBP/Blueprints и открываем Blueprint Class ThirdPersonCharacter. Добавляем плоскость "Plane" и задаем "Location" равным (0, 0, -90), "Scale"  (10, 10, 10) и применяем материал "M_VisionArea" к этой плоскости.

После этого нужно установить значение "Collision Presets" на "NoCollision", чтобы наша плоскость не взаимодействовала с другими объектами.


Теперь создадим переменные, которые позволят нам менять размеры и цвет области. Поместим их в категорию "VisionArea" и поставим галочку напротив "InstanceEditable", что позволит нам менять их в редакторе для каждого персонажа отдельно.

Переходим на вкладку "ConstructionScript" и создаем следующие узлы:

Узел 1. Создаем "DynamicMaterialInstance" из материала "M_VisionArea", записываем результат в переменную DMI и применяем этот материал к плоскости "Plane".

Узел 2. Масштабируем плоскость так, чтобы внешняя граница соответствовала значению, записанному в переменной "ExternalRadius". Делим на 50, так как используем стандартную плоскость из стартового набора.

В узлах 3, 4 и 5 мы связываем наши переменные с параметрами материала. Имя параметра ("ParameterName") должно соответствовать имени, указанному в материале.


Перейдем в окно редактора и добавим еще одного персонажа.

На картинке видно какие параметры в разделе "VisionArea" заданы у одного из персонажей и что область видимости скрывается внутри объектов, даже если высота этих объектов достаточно маленькая. Чтобы решить эту проблему, мы будем использовать материал "M_DecalVisionArea" и добавим возможность переключаться между двумя материалами в зависимости от ситуации. Логическая переменна "UseDecal" под эту задачу у нас уже добавлена.


Также нужно обратить внимание на зоны 1 и 2 - области в которых персонаж не может видеть объекты, но эти области все равно подсвечены зеленым. Этот недостаток наряду с простотой создания такой области и послужил причиной для появления слова "простой" в названии поста.


Добавим к нашему персонажу компонент "decal" и задаем "Rotation" равным (0, -90, 0). В "ConstructionScript" создадим узлы, аналогичные тем, что мы делали для обычного материала и компонента "Plane".

Видно, что отличия коснулись только верхних узлов. Так "DynamicMaterialInstance" мы создаем из материала "M_DecalVisionArea", вместо узла "Set Material" появился узел "Set Decal Material", а внешний радиус делится на 256 вместо 50 и результат подается на вход "y" и "z". Последние отличия связаны с тем, что "Decal" материал проецируется по направлению оси "x" на поверхности попадающие в его область действия и поэтому расположение осей отличается от расположения осей у плоскости.


Посмотрим какие изменения произошли в окне редактора.

Теперь область видимости показывается поверх невысоких объектов, кнопка "UseDecal" по-прежнему не работает и проблемы с зонами 1 и 2 никуда не делись.


Давайте настроим переключение материалов.

И добавим высоту, начиная с которой область видимости не будет отображаться.

Добавлена переменная "DecalHeight", "Set Relative Scale 3D" по оси "x" равен "1", добавлен узел "AddLocalOffset" и смещение подобрано таким образом, чтобы высота равная "170" соответствовала уровню глаз персонажа.

Заключение

Это мой первый опыт написания подобных статей, поэтому я не очень представляю насколько удачно изложен материал и уместна ли здесь такая тематика. Буду рад вопросам, конструктивным замечаниям и советам. Если тема данного урока кого-то заинтересует, то в следующий раз мы добавим затенение от объектов. Спасибо за внимание.


Ссылка на видеоурок:

Показать полностью 21 1
Отличная работа, все прочитано!

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества

Недвижимость и ремонт

Теги

Популярные авторы

Сообщества