프로토타입 발표까지 D-3, 이틀정도 더 구현할 시간이 남아있다. 지금까지 구현 내용을 좀 더 다듬고, 완성도를 올려볼 생각이다.
이 블로그를 참고해서 구현해보도록 하자! 정리가 너무너무 잘 되어있어서(원리부터 구현 방법까지 쭈욱 설명되어 있다) 읽어보는걸 추천한다 :) 나도 따라하면서 써보도록 하겠다.
먼저 커스텀 뎁스버퍼를 출력하기 위해 머테리얼을 하나 생성해주었다.
그리고 Material Domain을 Post Process로 변경해 주자.
그 다음, 레벨에 Post Process Volume을 생성해주고, infinite extent 체크(전체 레벨에서 확인 가능하게 해줌), Rendering Features의 Post Process Materials 배열에 PP_Highlight를 추가해주면 된다. (검색해서 찾으면 쉽다!)
아웃라인을 적용하고 싶은 오브젝트의 메시에서 Render CustomDepth Pass를 체크해준다.
PP_Highlight에서 SceneTexture를 노드를 추가하고, Custom Depth로 설정해준다.
SceneTexture : G-Buffer에 있는 이미지를 불러올 수 있다
Custom Depth에서 R값이 깊이값이기 때문에 R값만 마스킹 해야한다. ComponentMask를 추가하고, R에만 체크해주자. 지금은 출력값이 크기때문에 전체가 흰색으로 출력된다. 10000을 나눠주면 깊이 차이를 확인할 수 있다(숫자는 1번을 누르고 노드 배경을 클릭하면 생성된다. 여기다 10000을 써주고 나눠주면 된다). Custom Depth Pass에 등록된 물체가 깊이에 따라 가까울수록(값이 낮을수록) 검은색으로 보인다.
이렇게 노드를 작성해주고 Apply 해준 뒤 레벨로 돌아가보면,
CustomDepth에 등록된 액터만 검은색으로 화면에 나타난다.
이런식으로 8개의 픽셀을 모두 계산해준다. SceneTexelSize에서 단일 픽셀의 크기를 가져오고, 2차원 벡터로 만든 뒤 현재 픽셀을 호출해 Add해준다.
나머지 픽셀의 Custom Depth값까지 R값을 마스킹한 후 모두 더해서 주변 픽셀의 깊이값의 합을 구해준다.
주변 픽셀의 깊의 값의 합에 10,000,000과 같이 큰 값을 빼게 되면 mesh 내부의 픽셀은 음수가 되고, mesh 외부 픽셀과 mesh 경계에 있는 픽셀은 여전히 양수를 유지하게 된다. 이 값을 정규화해준다.
Mesh의 내부 픽셀만 검은색으로 출력되고 있다. 이제 아웃라인을 추출해보자. CustomDepth에서 mesh 외부 값은 0, 내부 값은 1이 되도록 정규화해준다.
계산된 두 값을 곱하면 테두리만 1이고 mesh 내부와 내부는 0인 결과를 얻을 수 있다.
선형보간 함수(Lerp)를 이용하면 장면에 원하는 테두리 색상을 출력할 수 있다. 선형 보간 함수는 A색상과 B색상, Alpha값을 입력으로 받는다. 그리고 Alpha값이 0인 픽셀은(테두리가 아닌 경우) A(장면을 텍스처에 렌더링 결과)가 출력되고 1인 픽셀(테두리의 경우) B의 색상이 출력된다.
Lerp함수의 A는 SceneTexture의 PostProcessInput0값을, B에는 원하는 테두리 색상을 넣어주고 방금 곱해서 구해준 값을 Alpha로 집어넣어주면 된다.
PostProcessInput0 : 최종 렌더링 이미지
그럼 이렇게 테두리가 그려진다!
이렇게 그리면 문제가 있다. 다른 메시에 가려진 테두리는 표시하고 싶지 않을 수 있다.
CustomDepth의 깊이값에 SceneDepth의 깊이값을 빼준다. 가까이 있는 물체일수록 깊이값이 작다. 그래서 양수일 경우 다른 물체에 가려진 것이고, 음수일 경우 다른 물체에 가려지지 않은 경우이다. 정규화를 통해 다른 물체에 가려진경우 0, 가려지지 않은 경우 1이 되도록 만들어주자.
방금 계산한 값으로 마스킹한 값을 출력한다.
이렇게 아웃라인이 다른 메시에 가려지는 모습을 볼 수 있다.
Texel에 size에 상수 값을 곱하면 테두리의 두께를 두껍게 만들 수 있다.
이렇게 곱해주면!
테두리가 두배 두꺼워졌다.
이제 아웃라인이 계속 출력되지 않게 Post process를 껐다켰다 하는 기능을 구현해야 한다.
// AMagneticPillar.h
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Components")
TObjectPtr<class UPostProcessComponent> Outline;
// AMagneticPillar.cpp
AMagneticPillar::AMagneticPillar()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
Box = CreateDefaultSubobject<UBoxComponent>(TEXT("Box"));
RootComponent = Box;
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
Mesh->SetupAttachment(Box);
Outline = CreateDefaultSubobject<UPostProcessComponent>(TEXT("Outline"));
Outline->SetupAttachment(Mesh);
Outline->bEnabled = false;
}
우선 자성 기둥에 포스트 프로세스 컴포넌트를 달아주었다. 그리고 이 C++파일을 상속한 블루프린트로 이동한 후, Post Process Materials에 방금 만들었던 머테리얼을 넣어주었다.
// AMagneticPillar.h
void SetOutline(bool Value);
// AMagneticPillar.cpp
void AMagneticPillar::SetOutline(bool Value)
{
Outline->bEnabled = Value;
}
포스트 프로세스 볼륨의 Enabled 변수를 제어하면 충분히 껐다켰다 하는게 가능할 것 같아서, 캐릭터가 자성 기둥을 탐색하는 함수에서 이 액터에 접근해 활성화해주는 함수를 호출시켜주는 식으로 구현해보려고 한다.
void ATrapperPlayer::FindMagneticPillar()
{
FVector Start = GetActorLocation();
FVector End = Start + GetControlRotation().Vector() * MagneticDistance;
DrawDebugLine(GetWorld(), Start, End, FColor::Green, false, -1);
FHitResult Result;
bool HasHit = GetWorld()->LineTraceSingleByChannel(
Result,
Start,
End,
ECC_GameTraceChannel1
);
if (HasHit)
{
AActor* Target = Result.GetActor();
TargetPosition = Target->GetActorLocation();
FVector BoxSize = Target->GetComponentsBoundingBox().GetSize();
TargetPosition.Z += BoxSize.Z + (GetCapsuleComponent()->GetScaledCapsuleHalfHeight() * 2);
bHasTarget = true;
// 갖고있는 타겟이 없을때 한번만 호출
if (!TargetMagneticPillar)
{
TargetMagneticPillar = Cast<AMagneticPillar>(Target);
TargetMagneticPillar->SetOutline(true);
}
}
else
{
// 방금까지 타겟이었던 자성기둥의 아웃라인을 해제해주고 nullptr
if (TargetMagneticPillar)
{
TargetMagneticPillar->SetOutline(false);
}
TargetMagneticPillar = nullptr;
}
}
Player가 자성기둥 포인터를 가지고있게 했다. bHasTarget 변수가 필요 없어졌으므로 삭제하고, bool변수를 사용하던 곳의 로직을 포인터가 존재할때만 동작하도록 변경했다. 갖고있는 타겟이 없을때(막 자성기둥을 포커싱으로 잡았을 때) 함수를 한번만 호출해도록 했고, 타겟이 해제될때(자성기둥 포커싱이 벗어날 때) 아웃라인 설정을 해제하고 nullptr을 대입해주었다.
하나를 활성화하면 다른 액터들도 전부 활성화되버리는 문제를 확인했다...
로그를 찍어보니 원하는 액터만 On / Off되는 것을 확인할 수 있었다. Mesh의 Render Custom Depth가 커스텀 뎁스에 액터를 추가시켜주는 식이니까 bRenderCustomDepth의 값을 바꿔주면 되겠다 싶었는데.. 그래도 똑같다.