전 포스팅에서 이야기했던대로 

얼굴 정상으로 돌려주고 시작합니다.

 

보시다싶이 우리가 현실적으로 받는빛은 만화에서 나오는 명암처럼 멋지게 안나옵니다.

더 과장되고 왜곡되야해요.

 

(우선 저 옷의 그림자는 dot으로 구현한것이니 안쓸거니까 지워줍시다.)

 

앞에 포인트 라이트 하나를 얼짱각도에 맞춰서 놔줍니다.

영상하는 친구가 뭐든간에 조명 세개는 써야 이쁘다고 말해줬던것이 생각나네요

대부분 실무에서도 포인트라이트가 아니더라도 저렴한 라이트를 이런식으로 놔줘서 보이게 할겁니다.(뇌피셜)

 

이것만으로는 뭔가 저는 부족합니다.

 

Vroid에서 어떤식으로 쉐이더 코드를 짜줬는지 궁금해서 베껴보기로 했습니다.

 

림라이트 + 저 머리부분은 빛의 영향을 안받도록 이미션맵 해줬네요.

 

요부분을 뜯어봐여.

 

 

rimLighting를 써줬네요 저거 설명 하려면 또 엄청 글 길어지니까 

 

림라이팅에 대해서는 한글로 구글에 쳐보면 좋은자료 많습니다. 

 

이미션은 특별한게 없네요 그냥 노드 짰던거 그대로 두면 됩니다.

 

보니까 lerp하면서 계속 col에다 합쳐주는 형식으로 결과물을 내더라구요. 

 

 

저거 기준으로 함 만들어봅니다.

 

 

다음과 같이 노드를 짜줍니다.

 

림라이트는 프레넬(Fresnel) 공식을 써서 만듭니다. 

 

위에 코드보시면 rimUV라 쓰고 dot연산쓰고 있는거 보이는데..

제가 알고있는 것보다 뭔가 더 복잡하게 림라이트를 구현해놨더라구요.

저거 분석하기 귀찮으니 그냥 잘 알려져있는걸 씁시다.

 

근데 언리얼에서 fresnel을 만들어놨어요! 우와! 킹리얼!

 

저 모델에는 앞머리는 add 뒷머리는 lerp로 한겁니다.

 

add는 그냥 더하는것에비해 lerp는 두개를 입력받아서 사잇값을 출력하는 것이므로 어둡게 나옵니다.

 

add가 더 싸고 이쁘게 나오니까 add 쓰는걸로 결정

 

 

머리카락과 얼굴에 림라이트를 적용한 모습입니다.

 

 

유니티에서 그려지는거하곤 좀 많이 다른모습이지만 

 

저는 제가 만든게 더 맘에듭니다 힣ㅎ

 

 

문득 생각난건데 .

 

눈동자는 빛을 흡수하는게 맞기때문에 다음과같이 노드를 짜서 수정해줍시다.

 

 

before -> after

 

추가로 앞머리 림라이팅이 너무 하얗고 넓어서 떡진머리같으니까 노란색으로 해줍니다.

 

다음포스팅은 저기다가 아웃라인 (외곽선)을 넣어보고 눈썹을 가리는 머리카락을

반투명으로해서 눈썹이 보이도록 해볼까합니다.

 

아웃라인은 같은 메쉬를 두번 그리는 거라 무거워져서 어짜피 안쓸거지만 그냥 공부하는거 정리하는겸 해볼거에요.

 

 

ramp2.TGA
0.00MB

1. 언리얼 에디터상에서 좌측 탭에서 포스트 프로세스 볼륨을 가져옵니다.

 

2. 디테일에서 unbound를 체크해줍니다. (볼륨안에서만 되는것이 아닌 글로벌로 설정하는것입니다.)

 

3.임의의 머테리얼을 만들어서 오른쪽클릭 -> sceneTexture 를 생성하고.

 

 

 

디테일을 다음과같이 설정해줍니다.

 

씬텍스쳐가 뭔지 간단하게 설명하자면

 

현재 화면에 렌더링되고있는 장면 자체를 텍스쳐로 취급하는겁니다.

 

예를들어 씬텍스쳐: 디퓨즈맵이 뭐냐면 

이렇게 빛의 영향을 받는물체의 순수 텍스쳐를 반환합니다.

얼굴은 언릿이니까 까맣게 나오는게 당연하구요.

 

이런겁니다.

 

마스크가 뭐냐면.

 

텍스쳐의 RGB의 한채널의 색상만 출력하겠다는 겁니다. 

 

 

저는 처음볼때 이런생각했습니다 R이면 빨간색은 출력해야되는거 아니야? 했는데.

사실 그런의미가아니라

 

R채널(빨간색)의 채도만큼 흑(0)~백(1)으로 출력하겠다는 겁니다.

그러니 어떤색을 마스킹하던 흑백으로나오는것은 똑같습니다.

 

 

위 이미지는 포스트프로세스 볼륨에 렌더링되고있는 그냥 원래 렌더링텍스쳐와 디퓨즈를 나눈 결과입니다.

 

순수 텍스쳐를 제외하고 그림자만이 저렇게 남게됩니다.

 

그리고 이제 ramp텍스쳐가 필요하니 만들어 봅시다.

 

  포토샵에서 다음과같은 사이즈로 만듭니다.

 

 

이렇게 색상이 나누어진 빼뺴로를 만들어서 언리얼에 드래그해서 임포트합니다.

 

그리고 다음과같이 타일링 설정을 합니다.

 

wrap., clamp , mirror 세개가있는데.

 

저거 뜻이 뭐냐면 저 이미지를 바탕화면으로 설정했을때.

 

이렇게 이미지에 비해 공간이 클시 x축은 어떻게 Y축은 어떻게 하겠냐는겁니다.

 

wrap은 위처럼 그냥 반복해서 타일링해서 채우겠다는 것이고

clamp는 부족한만큼 모짜렐라 치즈마냥 쭈우욱 늘려서 채우겠다는것이며

mirror은 좌우대칭으로 타일링해서 채우겠다는겁니다.

 

그리고 SRGB의 체크를꺼줍니다.

 

 이제 이 램프텍스쳐를 사용합니다.

 

다음과같이 노드를 짭니다.

 

포스트프로세싱0은 0번 볼륨의 렌더링텍스쳐를 가져오고 거기서 디퓨즈컬러를 나누면 

 

렌더링되고있는 씬의 순수 그림자만 남습니다.

 

마스크를 씌운이유는 RGB 3벡터변수말고 하나의 스칼라값을 사용해야하며

 

클램프를 사용하는 이유는 uv는 기본적으로 0~1까지의 좌표를 사용하기때문입니다.

 

입력받은 uv좌표를 기준으로 텍스쳐좌표의 색깔을 그리는데 사용합니다. 

 

사실 저는 처음에 uv좌표에 대해 이해하는게 쉽지가 않았어요 TexCoord에 대해서 알아보고 옵시다.

 

https://cafe.naver.com/unrealenginekr/26327

 

TextureCoordinates를 ...

대한민국 모임의 시작, 네이버 카페

cafe.naver.com

오셀롯님이 작성해주신 글입니다

 

그러면 다음과같이 그려지게되는데.

 

회색부분이 그림자가 드리우는 부분이라고 볼수 있습니다.

 

이대로는 사용하기 어렵죠.

 

그러니까 여기다가 디퓨즈맵을 한번 곱해줍니다.

 

 

이제 뭔가 만화그림자같이 만들어졌죠?

 

저 까만 얼굴 계속보니까 무섭네요.  다음포스팅에서 원래대로 돌리겠습니다ㅎ 

 

다음에는 그림자 들어오는걸 예쁘게 조금더 다듬어주고 빛의방향을 내가 보는 방향으로 바꿔주는 내용에

대해 포스팅 하겠습니다. 

 

GDC에서 발표한 길티기어 툰쉐이딩그냥 한번 봐봐여

https://www.youtube.com/watch?time_continue=1717&v=yhGjCzxJV3E

 

Const &char Class::Method (int* pt)

 

해당 메소드의 반환값(char)을 상수화시켜서 못바꾸게 하겠다는뜻.

 

이걸 그대로 받아서 변수로쓰면 못바꿈.

 

&char Class::Method (int* pt) Const

 

메소드 자체를 상수화 하겠다는뜻.

 

메모리스택에서 얘가 다 돌고 해제될때까지 

 

내부 데이터들이 다 상수화되서 안바뀜. 

 

스레드,동시성 프로그래밍할떄 유용할거같다.

 

&char Class::Method (Const int* pt) 

 

pt라는 포인터가 가리키는 매개변수의 주소값을 상수화시켜버리는것.

 

어디가리키는지는 못바꾸는데 가리키는곳의 데이터는 바꿀 수있다.

 

 

&char Class::Method (int* Const pt) 

 

pt라는 포인터가 가리키는 매개변수의 데이터를 상수화 시켜버림.

 

포인터가 가리키는 주소는 바꿀수있는데 데이터를 못바꿈

 

 

규칙성을 따져보면

const는 앞에 뭔가 있으면 그거 하나를 얼려버림.

그리고 앞에 아무것도 없고 뒤에 뭔가 있으면 그걸 다 얼려버림.

 

const를 어떻게 넣냐에 따라서 

프로그래머가 원치않게 변수조작하는것을 미연에 방지해줄수 있음

저번처럼 바인딩할 버튼을 만들고 

인터페이스에 가상함수를 만들고.

 

 

상속받은 인터페이스를 가져오고.

 

 

 

인터페이스 기능을 구현. (핵심)

 

 

 

메뉴에서 인터페이스의 함수를 호출할 함수를 작성 .

그리고 버튼이벤트 바인딩.

 

 

 

 

 

 

 

UMG와 부모위젯헤더파일에 이렇게 바인딩시킨후.

 

클릭했을떄 함수 등록

 

마지막줄의 인터페이스함수를 호출하는 형식으로 동작하는데 구성은 이렇다.

인터페이스 선언 -> 인스턴스에서 다중상속 -> 구현  ->

메인메뉴에서 여기서 구현한 인터페이스를 받아서 호출함. 

 

결과

 

 

 

프로젝트 파일로 가서 source -> 프로젝트이름 폴더 -> 프로젝트이름 .build.cs 라는 c#문서를 열어서 UMG를

추가해준다.

 

에디터에서 마우스오른클 ->유저 인터페이스 -> 위젯 블루프린트를 만들고 대충 저런 모양 나오게 

셋팅한다 (c++보는사람은 블루프린트 이미 알고있을거라 생각하니 사용법은 생략)

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "PuzzlePlatformsGameInstance.generated.h"

/**
 * 
 */
UCLASS()
class TEST_API UPuzzlePlatformsGameInstance : public UGameInstance
{
	GENERATED_BODY()
		//c++ 생성자 단에서 초기화 
		UPuzzlePlatformsGameInstance(const FObjectInitializer& ObjectInitializer);
	
public:

	//언리얼 게임인스턴스 단에서 초기화 
	virtual  void Init();

	UFUNCTION(Exec)
		void Host(); //호스트

	UFUNCTION(Exec) //조인 + 접속아이피
		void Join(const FString& Address);

	UFUNCTION(BlueprintCallable)//메뉴불러오는 함수
		void LoadMenu();

private:
	//위젯가져오기
	TSubclassOf<class UUserWidget> MenuClass;
	//메인메뉴 클래스 포인터
	class UMainMenu* Menu;
	
};

위젯을 다룰 인스턴스 소스의헤더파일에 블루프린트에서 사용할수있도록 위와같이 함수선언 +

위젯클래스를 가져올수 있도록 해준다.

 

void UPuzzlePlatformsGameInstance::LoadMenu()
{
	if (!ensure(MenuClass != nullptr)) return;

	UUserWidget* Menu = CreateWidget<UUserWidget>(this, MenuClass);
	if (!ensure(Menu != nullptr)) return;

	Menu->AddToViewport(); //뷰포트에 나타내기
}

그리고 cpp파일에서 이렇게 코딩해준후 

 

레벨블루프린트에서 다음과 같이 스크립트를 짜준다.

 

게임화면이 이같이 뷰포트에 덮어씌워진다.

 

 

위와같이 나오는데. 캐릭터 asdw로 움직이고 회전 그대로되면서 

 

버튼을 누르고싶은데 마우스커서가 안나옴.

 

그러니까 포커스가 위젯에 맞춰져야한다.

 

블루프린트로는 이런거.

 

 

그러니까 다음과같이 코딩.

 

void UPuzzlePlatformsGameInstance::LoadMenu()
{
	if (!ensure(MenuClass != nullptr)) return;

	UUserWidget* Menu = CreateWidget<UUserWidget>(this, MenuClass);
	if (!ensure(Menu != nullptr)) return;

	Menu->AddToViewport(); //뷰포트에 나타내기

	//현제 이용중인 플레이어 컨트롤러를 얻어옴.
	APlayerController* PlayerController = GetFirstLocalPlayerController();
	if (!ensure(PlayerController != nullptr)) return;

	//사용자 입력을 처리해줄 라이브러리
	FInputModeUIOnly InputModeData;
	//사용랗 위젯을 지정
	InputModeData.SetWidgetToFocus(Menu->TakeWidget());
	//마우스를 뷰포트에 고정할건지 자유롭게 놔둘건지 지정
	InputModeData.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);

	//위에서 구현한사항을 플레이어 컨트롤러에서 지정
	PlayerController->SetInputMode(InputModeData);
	//마우스커서 보이게하기
	PlayerController->bShowMouseCursor = true;
}

이렇게하고 컴파일하면 키보드입력이 안먹고 마우스커서가 나타나서 버튼을 누를 수 있게된다.

 

그러나 아직 버튼을 눌러도 아무 동작을 하지 않는다 

 

당연히 바인딩을 안해줬으니까.

 

 

준비물.

 

 

usetWidget을 부모로한 c++클래스를 

다음과같은 이름으로 생성해준다.

 

 

 

 

 

 

 

 

 

 

저번에 만든 umg에서 

1.그래프탭 선택.

2.클래스세팅

3.부모클래스를 아까 만든 c++클래스로 설정해준다.

헤더파일에 바인딩할 버튼들을 스샷처럼 코딩하는 방식.

 

주의. 이름똑같이 해줄것

 

mainmenu의 헤더파일

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"

#include "MainMenu.generated.h"


/**
 * 
 */
UCLASS()
class TEST_API UMainMenu : public UUserWidget
{
	GENERATED_BODY()

public:
	
protected:
	//초기화 + 유무를 검출하기위해 bool반환
	virtual bool Initialize();

	



private:

	//UMG의 버튼과 코드를 바인딩하기위한 버튼선언.

	UPROPERTY(meta = (BindWidget))
		class UButton* Host;

	UPROPERTY(meta = (BindWidget))
		class UButton* Join;

	UFUNCTION() // 호스트가 되는 함수
		void HostServer();

	
};

 

mainmenu의 소스파일.

bool UMainMenu::Initialize()
{
	bool Success = Super::Initialize();
	if (!Success) return false;

	if (!ensure(Host != nullptr)) return false;

	UE_LOG(LogTemp, Warning, TEXT("succecess!!!"));
	//호스트 버튼이 눌렸을때 HostSever 함수가 호출되도록 바인딩.
	Host->OnClicked.AddDynamic(this, &UMainMenu::HostServer);

	return true;
}

void UMainMenu::HostServer()
{
	UE_LOG(LogTemp, Warning, TEXT("I'm gonna host a server!"));

	
}

 

이제 호스트 버튼을 누를때마다 로그가 뜨는것을 확인 할 수있다.

 

 

 

include <memory.h> 헤더파일을 포함시켜야함

유니크포인터.

unique_ptr -->이 포인터가 한 인스턴스를 가지면 다른 포인터가 얘를 가리키려고할때 컴파일에러로 막는다.

또 유니크포인터가 스코프를 탈출할때 소멸자단에서 메모리를 해제해준다.

순수c++11 --> std::unique_ptr<타입> 이름(new 타입(매개변수))

c++14--> std::unique_ptr<타입> = make_unique<타입>(매개변수)

 

언리얼c++ -->TUniquePtr

https://api.unrealengine.com/INT/API/Runtime/Core/Templates/TUniquePtr/index.html

 

 

공유포인터.

c++14이후 나왔다.

유니크 포인터와 달리 얘는 여러포인터가 나를 참조할수있다.

다만 내가 가지고있는 데이터와

내가 몇개의 포인터한테 공유되고있는지 카운팅한다.

강한참조횟수와 약한참조횟수를 카운팅함. 이외에 잡다한기능 몇개 있음.

 

 

Uobject가 이에 해당된다.

참조횟수가 카운팅되고 GC가 메모리를 회수해간다.

Newobject< >로 메모리할당이 된경우

Uobject::ConditionalBeginDestroy() 멤버함수로 수동 메모리해제도 가능하다.

이 이하는

이미 c++절에서 설명했고

언리얼 레퍼런스보다 더 정리를 잘할수 없어서 그냥 링크남기고 ㅌㅌ 하겠따.

주의사항 짧게하자면 공유포인터는 선언할때 초기화 안하면 안되고

isvalid로 유효성 검사를 할수있다.

공유레퍼런스는 널포인터를 가리키는게 안되서 안전하니까.

왠만하면 공유 레퍼런스 쓰란다.

http://api.unrealengine.com/KOR/Programming/UnrealArchitecture/SmartPointerLibrary/SharedPointer/

 

약한포인터.

공유포인터가 지들끼리 강한참조로 서로 공유하고있으면 서로 참조할 경우.

가비지컬렉터가 돌때 참조횟수를 기준으로 검열을하는데

이때 서로 공유하고있는 애들끼리 무한으로 참조하고 앉아있으면 가비지컬렉터는 얘는 쓰레기가 아닌줄알고

메모리에 계속 냅둔다 이것의 순환참조의 문제다. .

그래서 약한 포인터가 나왔다.

무조건 공유포인터 하나를 주인으로 참조하고있어야 하는데.

약한포인터는 공유포인터가 없어지면 주인따라서 같이 죽어버린다. 때문에 메모리에 남는일이 없다.

그치만 , 그 이유때문에 약한포인터는 참조한다고했을때 언제 없어질지몰라서 위험하다.

떄문에 lock으로 잠가서 넘겨주는것이 있다.

아래코드는 예들들어 설명.

 

http://api.unrealengine.com/KOR/Programming/UnrealArchitecture/SmartPointerLibrary/WeakPointer/index.html

세팅하기

  이렇게 c++기반 게임인스턴스 하나 만들어요

  맨마지막거가 게임인스턴스입니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

서드퍼슨맵을 복사해서 로비맵을 생성.

둘이 서로 알아먹기만 할수있게 표시하면됨 

 

 

 

 

 

맵 &모드에서 게임시작하면 맨처음나오는 맵과 서버가 맨처음 나오는맵을 설정

 

  

생성자초기화와 언리얼에서 제공하는                   게임 인스턴스 초기화의 차이.

 

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "PuzzlePlatformsGameInstance.generated.h"

/**
 * 
 */
UCLASS()
class TEST_API UPuzzlePlatformsGameInstance : public UGameInstance
{
	GENERATED_BODY()
		//c++ 생성자 단에서 초기화 
		UPuzzlePlatformsGameInstance(const FObjectInitializer& ObjectInitializer);

	//언리얼 게임인스턴스 단에서 초기화 
	virtual  void Init();

	UFUNCTION(Exec)
		void Host(); //호스트

	UFUNCTION(Exec) //조인 + 접속아이피
		void Join(const FString& Address);

	
};

 

 

 

// Fill out your copyright notice in the Description page of Project Settings.

#include "PuzzlePlatformsGameInstance.h"

#include "Engine/Engine.h"

UPuzzlePlatformsGameInstance::UPuzzlePlatformsGameInstance(const FObjectInitializer & ObjectInitializer)
{
	UE_LOG(LogTemp, Warning, TEXT("GameInstance Constructor"));//생성자 초가화
}

void UPuzzlePlatformsGameInstance::Init()
{
	UE_LOG(LogTemp, Warning, TEXT("GameInstance Init")); //인스턴스 초기화
}

void UPuzzlePlatformsGameInstance::Host()
{
	UEngine* Engine = GetEngine(); //게임엔진의 현재 인스턴스를 가져움

	if (!ensure(Engine != nullptr)) return;

	Engine->AddOnScreenDebugMessage(0, 2, FColor::Green, TEXT("Hosting")); //로그띄워줌

	UWorld* World = GetWorld(); // 레벨의 현재 레벨을 가져옴

	if (!ensure(World != nullptr)) return;
	//서버전용 .  서버를 입력한 경로의 맵으로 이동시킴, 클라이언트랑 같이감 서버가 접속중인 클라이언트들의
	//플레이어 컨트롤러에서 ClientTravel을 호출함.
	World->ServerTravel("/Game/ThirdPersonCPP/Maps/ThirdPersonExampleMap?listen");
}

void UPuzzlePlatformsGameInstance::Join(const FString& Address)
{
	UEngine* Engine = GetEngine();

	//현재 클라의 플레이어컨트롤러의 첫번째 플레이어 컨트롤러 가져옴
	APlayerController* PlayerController = GetFirstLocalPlayerController();

	if (!ensure(Engine != nullptr)) return;
	//로그
	Engine->AddOnScreenDebugMessage(0, 5, FColor::Green, FString::Printf(TEXT("Joining %s"), *Address));

	//아이피가 유효할때 호출되면 입력된 아이피주소의 서버로 이동함.  
	//인텔리센스 빨간줄 무시하셈.
	PlayerController->ClientTravel(Address, ETravelType::TRAVEL_Absolute);

	
}

 

결과.

 

 

 

+ Recent posts