ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Unreal] Render Through Object, 오브젝트 뒤에 있는 플레이어 보이게 하기
    쾌락없는 책임 (공부)/Unreal 2023. 7. 12. 10:14
    반응형

    개요

    Apex Legend의 블러드 하운드, 오버워치의 위도우 메이커. 이 캐릭터들의 스킬에서 공통점이 있다면 오브젝트 뒤에 있는 적들의 모습을 볼 수 있다는 점입니다. 이를 통해 보이지 않는 적들을 찾아낼 수 있습니다. 

     

    이런 요소를 제작하고 있는 게임에 넣어보고자 Post Process Volume을 활용했고 이 과정을 한번 정리해 보려고 합니다.

     

     

    Post Process Material 제작 개요

    일단 Content Browser에서 우클릭을 해서 Material을 만들어 줍니다. 저 같은 경우 PP_SearchOutEffect라는 이름을 사용했습니다.

     

    만든 Material의 Domain을 Post Process로 설정해 줍니다. 이제 본격적인 개념을 알아봅시다.

     

    중간에 아무 장애물이 없는 경우 Custom Depth와 Scene Depth는 동일한 값으로 나오게 됩니다. 여기서 Custom Depth는 월드에서 카메라 to 오브젝트의 거리라고 생각하면 되고 Scene Depth는 최종적으로 그려지는 화면에서 오브젝트까지 거리라고 생각하면 됩니다.

     

    일단 위 사진에서는 중간에 다른 장애물이 없기에 이런식으로 나오게 되는데 중간에 장애물을 넣게 되면 이야기가 달라지게 됩니다.

     

    중간에 오브젝트가 있으면 랜더링에서 무시하게 됩니다. 이는 실제 현실에서도 동일한 이야기입니다! 

     

    그렇게 되면 화면은 저 장애물을 그리게 될 것이고 Scene Depth는 50이 되게 됩니다. 하지만 오브젝트와의 거리는 여전히 100이기 때문에 Custom Depth는 그대로 유지가 되는 것이죠.

     

    여기서 이제 Custom Depth가 Scene Depth보다 크면 빨간색으로 투시해서 그려줄 계획입니다.

     

     

    Material 만들기

    일단 Custom Depth와 Scene Depth를 가져와 비교해 줍니다. Mask R을 통해 Depth 값만 가져와 비교할 수 있게 됩니다.

     

    이후 Custom Depth에서 한 작업 순서는 아래와 같습니다.

    1. Custom Depth값을 6000으로 나누어줌 (이 6000이 거리가 되며 이 이상의 거리가 되면 영향을 받지 않게 됩니다)
    2. One Minus노드를 통해 값을 반전해 줍니다.
    3. 이후 Clamp를 해 줍니다. (Clamp를 한 이유는 이후 Lerp를 사용하기 위함입니다)
    4. 최종적으로 Custom Depth가 6000안으로 들어오면 1의 값, 아니면 0의 값이 나오게 됩니다.

     

    그런 다음 아래 Scene Depth의 작업 순서입니다.

    1. Custom Depth값에서 Scene Depth값을 빼 줍니다.
    2. 위 예시 그림 2개에서 후자(중간 장애물이 있는 경우)인 경우 양수가 나오게 되고 아닌 경우 0이 나오게 됩니다.

     

    이렇게 나온 값을 곱하면 둘 다 양수라면 양수가 나오고 하나라도 0이라면 0의 값이 나오게 될 것입니다. 둘 다 1이 되어야지만 중간에 걸림돌이 있는 경우가 되니 이 경우에만 그려줄 것입니다.

     

    마지막으로 Lerp(Alpha 를 0~1로 받아 블랜딩 해주는 노드)를 사용할 것이니 Ceil을 통해 소수점 반올림을 해 0, 1로 값이 나오게 해 줍니다.

     

    위에서 만든 Ceil의 반환값을 Lerp의 Alpha로 넣어주고 위처럼 노드를 만들어 줍니다. 지금 보니 Alpha까지 사용하지 않아도 만들어질 것 같네요.

     

    이렇게 하면 Alpha(Custom Depth, Scene Depth로 계산한 결과)가 0이라면 중간 걸림돌이 없는 오브젝트니 그냥 그려주게 되고(Post Process Input) 1이라면 중간 걸림돌이 있다는 것이니 저 색 노드로 그려주게 됩니다.

     

     

    추가사항 - Post Process를 사용하는 게 많다면

    저 같은 경우 상호작용 가능한 액터들 매쉬에도 Post Process를 활용해 테두리를 그려주는 효과를 만들었습니다. 이렇게 되면 두 효과가 겹쳐서 나오기 때문에 이를 구분할 필요가 있습니다. 테두리가 필요한 애들과 이런 투시 효과가 필요한 애들.

     

    그래서 Custom Stencil을 활용하기로 했습니다.

     

    이렇게 해서 Custom Stencil이 100인 경우에만(A==B에 위 결과값을 넣어줌) 투시 효과를 그리고 했고 아닌 경우 그냥 그리는 것으로 처리했습니다.

     

    만일 다른 Post Process가 없고 추가할 계획도 없다면 위 항목에서 나온 결과를 바로 Emissive Color에 넣어주면 됩니다. 그리고 이를 했는데 제대로 나오지 않는다면 아래 포스트를 참고해 보세요. Stencil 설정이 꺼져 있을 수도 있습니다.

     

    [Unreal] CustomDepth Stencil Value 결과가 이상한 경우

    개요 인게임에 Custom Depth를 사용하는 효과가 2개 있습니다. 하나는 인터렉팅이 가능한 오브젝트들에 보이는 아웃라인 효과, 벽 뒤 플레이어를 감지할 수 있는 투시 효과. 그런데 아웃라인 효과와

    husk321.tistory.com

     

     

    이후 Mesh, Post Process Volume 설정하기

    일단 투시를 할 Mesh들에서 Custom Depth를 Enable 해줘야 합니다. 위 그림에서 Render Custom Depth Pass를 체크하면 설정할 수 있습니다. 저같은 경우 아래 항목을 통해 C++ 코드로 제어해 줄 예정이니 체크가 해제되어 있는 모습입니다.

     

    추가로 위 Stencil 노드를 설정했다면 이에 맞게 값도 설정해 주시면 됩니다.

     

    그런 다음 레벨에 Post Process Volume을 추가하고 이후 Post Process Materials에 본인이 만든 Material을 추가해 주면 됩니다.

     

    이후 범위를 설정해주면 되는데 저는 맵 전역에서 사용되게 할 예정이라 Infinite Extend 옵션을 체크해 줬습니다. 여기까지 했다면, 위 Mesh에서 Render Custom Depth Pass를 체크했다면 아래 사진처럼 벽 너머로 잘 보이게 될 것입니다.

     

     

     

    여기서 더 나아간다면

    저같은 경우 여기서 더 나아가 아이템을 사용하면 근처 거리에 있는 플레이어들 5초 동안 보이게 하는 기능을 만들어야 합니다. 일단 플레이어의 Custom Depth를 활성화하고 비활성화할 인터페이스를 만들고 플레이어가 이를 상속받게 하면 됩니다.

    class PROJECTFA_API ISearchOutEffectable
    {
    	GENERATED_BODY()
    
    public:
    	/*
    	 * @brief Enable custom depth for customdepth stencil value 100
    	 */
    	virtual void EnableSearchOutEffect() {}
    
    	/*
    	* @brief Disable custom depth for customdepth stencil value 100
    	*/
    	virtual void DisableSearchOutEffect() {}
    };

    이후 플레이어에서 상속받아 아래처럼 구현해 줬습니다.

    void APlayableCharacter::EnableSearchOutEffect()
    {
    	GetMesh()->SetRenderCustomDepth(true);
    	GetMesh()->CustomDepthStencilValue = 100;
    }
    
    void APlayableCharacter::DisableSearchOutEffect()
    {
    	GetMesh()->SetRenderCustomDepth(false);
    }

     

    이제 위 인터페이스를 가진 액터들을 검출할 아이템을 만들었습니다. 이 아이템은 사용되면 근처 반경에 있는 액터들을 가져와 인터페이스를 불러올 예정입니다.

     

    UCLASS()
    class PROJECTFA_API ASearchOutItem : public APickupItem, public IInventoryUsable
    {
    	GENERATED_BODY()
    
    private:
    	UPROPERTY()
    	TArray<AActor*> SearchOutList;
    
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Search Out", meta = (AllowPrivateAccess = "true"))
    	float SearchOutTime;
    	FTimerHandle SearchOutTimer;
    
    public:
    	ASearchOutItem();
    	
    	virtual void InventoryAction_Implementation() override;
    	virtual void RemoveFromInventoryAction_Implementation() override;
    	virtual void UseAction() override;
    private:
    	void ResetSearchOutActors();
    };
    void ASearchOutItem::UseAction()
    {
    	TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypeQuery;
    	TArray<AActor*> ActorsToIgnore;
    	TArray<AActor*> OutActors;
    	UKismetSystemLibrary::SphereOverlapActors(this, GetActorLocation(), 1000.f, ObjectTypeQuery,
    		ACharacter::StaticClass(), ActorsToIgnore, OutActors);
    
    	for(auto SearchedActor : OutActors)
    	{
    		if(UKismetSystemLibrary::DoesImplementInterface(SearchedActor, USearchOutEffectable::StaticClass()))
    		{
    			SearchOutList.Emplace(SearchedActor);
    		}
    	}
    
    	for(auto SearchOutACtor : SearchOutList)
    	{
    		if(auto SearchOutEffectableActor = Cast<ISearchOutEffectable>(SearchOutACtor))
    		{
    			SearchOutEffectableActor->EnableSearchOutEffect();
    		}
    	}
    
    	if(GetWorld())
    	{
    		GetWorld()->GetTimerManager().SetTimer(SearchOutTimer, this, &ASearchOutItem::ResetSearchOutActors, SearchOutTime);
    	}
    
    	ItemDroppedEvent.Broadcast(this);
    }
    
    void ASearchOutItem::ResetSearchOutActors()
    {
    	for(auto SearchOutACtor : SearchOutList)
    	{
    		if(auto SearchOutEffectableActor = Cast<ISearchOutEffectable>(SearchOutACtor))
    		{
    			SearchOutEffectableActor->DisableSearchOutEffect();
    		}
    	}
    
    	if(GetWorld())
    	{
    		GetWorld()->GetTimerManager().ClearAllTimersForObject(this);
    	}
    
    	Destroy();
    }

    이 아이템의 경우 위 UseAcion함수를 인벤토리에서 부를 수 있게 처리했으며 UKismetSystemLibrary::SphereOverlapActors 함수를 통해 주변 범위의 엑터들을 가져왔습니다.

     

    이렇게 하면 아이템을 사용했을 때 벽 뒤에 있는 플레이어를 볼 수 있게 됩니다. 정확히는 저 인터페이스를 상속받은 액터들이죠. 이후 추가 개발을 하면서 사용자 본인을 제외하고 상대방만 보이게 처리해 주면 완벽합니다.

    반응형

    댓글

Designed by Tistory.