dot연산을 이용해 옷에다 적용한 툰쉐도우. (아무튼 그림자임 )

 

원래는 디렉셔널 라이트로해야하지만 

스팟라이트가 보기 쉬우니까 이걸로.

 

콘텐츠브라우저 오른클릭 -> 머테리얼 -> 머레리얼 파라미터 콜렉션 

생성합니다.

 

 

액터에다가 라이팅 할것도아니고  포스트 프로세스 볼륨안에 넣기 귀찮으니 그냥 레벨블루프린트 안에다가 합시다.

 

요로케 빛의 정방향을 얻어옵니다.

 

그리고 원피스 머테리얼 편집.

 

*0.5+0.5는 half lambert라고해서 내적값의 범위가 1,-1 가서 검은색이 넓게 퍼지게되는데.

이것을 1~0까지만 되게 해주는 마법의 공식입니다 

 

dot을 모르겠으면 

 

대마왕님의 블로그에가서 공부해봅시다.

14 15강 보면 있음

https://chulin28ho.tistory.com/81?category=822622

 

ShaderFX를 이용한, 그래픽 디자이너를 위한 기초 쉐이더 강좌 14강

DOT 제대로 알기 14강입니다. 반갑습니다. 흠... 간만에 보니까 막 낯설고 막 이래. 전에 뭔 얘기 했지? 라는 생각에 13강 잠깐 보고 올께요 아... 그거 했구나... 13강때 드디어 Shader의 본질에 접근한 거였군..

chulin28ho.tistory.com

 

수학을 모르겠으면 

https://www.youtube.com/watch?v=P3U9DXFp9SA&list=PL-xqYJ8bjgMC-p94R7iXjFCs-znRg93PJ&index=12

 

원리는 이렇고 

 

문제가 두가지 있읍니다.

 

 

이렇게 라이트벡터가 캐릭터를 안봐서 밤이되거나해서 위를 볼때 

아래에서 비추는 귀신같은 조명이 되어버립니다. 

 

그리고 쉐이더는 dot 연산을 포함한 삼각함수는 나눗셈보다 훨씬 무겁다

+ if문같은 분기문도 중첩되면 무겁다. = 남용하지 말것

 

왜 무겁냐면 컴터가 기본적으로 이진수를 사용하고 나눗셈과 삼각함수의 연산과정을 보면 이해가 될겁니다.

if 분기문또한 내부적으로 빼기연산이지만

초당 30프레임을 렌더링할때마다 1프레임당 최소 몇백 픽셀에 저 연산을 한다고 생각하면.. 음 좀 에바임..

 

게다가 안이쁨.

 

그러니까 다음포스팅에서는 ramp텍스쳐를 활용해 최적화 + 내가원하는 그림자색깔 넣기를 포스팅 하겠습니다.

 

 

이경우는 라이트를 사용하지 않았을때다

 

하지만 언리얼에서 뷰포트모드에서 라이트에서 꺼주고 ini파일을 바뚸준들 shipping 으로 빌드하면 

적용 안되서 이건 아무 쓸모가 없다 히히

 

자 저번 포스트 따라했는데 라이트 포함을 켜면 

 

다이렉트 라이트 영향을 받는 캐릭터가 나올것이다.

 

이제 매트리얼을 더블클릭해서 노드 편집을 해주자.

 

쉐이딩 모델을 언릿으로 하자.

 

그러면 아웃풋 모델에 저렇게 이미시브 컬러만 적용할수있고 나머지 핀은 다 적용이안된다.

 

깜빡하고 안적었는데 곱하기와 더하기의 단축키는 m+왼클 a+ 왼클이다.

 

rbg값으로 설명하자면 0.0.0 은 검은색 111은 흰색이다.

 

어차피 이 글 보는사람은 프로그래머가 아니라고 가정해서 설명한다면.

 

곱하기 연산은 말그대로 저 맵의 rbg값에 노란색 rbg값을 곱해서 혼합시킨다는 개념이고

 

더하기연산은 덮어씌운다는 개념으로 이해하도록 하자.

 

 

 

하면 요로코롬 나온다.

 

근데 먼가 뿌얗다 색감이 안살아요.

 

맨 처음 스샷이랑 너무 차이나자너 

 

왜그렇냐면.

 

언리얼은 라이팅을 정말 대충해줘도 때깔이 정말 좋은이유가 

 

언리얼 내부에서 색 보정해주는 톤매퍼와 포스트 프로세싱이 자동으로 켜져있는게 있다.

 

이것에 대한 연관내용이 숨어있으니 열심히 구글링해서 찾았다.

 

http://api.unrealengine.com/KOR/Engine/Rendering/PostProcessEffects/ColorGrading/

 

컬러 그레이딩 및 필름 톤매퍼

씬 컬러 조절을 위한 톤매핑과 색보정 이펙트입니다.

api.unrealengine.com

 

자세한것은 이 문서를 참고.

 

무튼 언리얼에서 기본으로 적용된 톤맵퍼기능이 언릿계열과 상성이 안맞는 경우가 꽤 있다. 

 

~을 눌러 콘솔창에 

 

라고 입력해주자.

 

 

 

그러면 짠

 

머리카락 떄깔이 돌아왔다.

 

그리고 빛에 영향을 받지않기때문에 

 

포인트 라이트를 가까지줘서 보면 머리카락과 얼굴의 그림자는 안생기고 

 

눈깔과 머머리그림자만 나오는걸 알 수있다.

 

 

딱 여기까지는 내가 혼자 만들고싶었던 Vtuber 쉐이딩이다.

 

Vtuber는 라이팅이 필요없으니 여기까지만해도 내가 원하는 모델링 갖고와서 VR장비와 IK를 통해

 

속은 다리털 아저씨지만 겉은 미소녀로 사기를 칠수있다.

 

 

 

하지만 다음 포스팅에서 조금 더 욕심을 부려서

 

unlit을 그대로 사용하지 않고 

 

쉐이더 연산에저 자주쓰는 dot(벡터의 내적)을 사용해서 

 

빛을 받으면서도 만화같은 명암을 주는 셀쉐이딩에 대해서 포스팅 하겠다. 

전 게시물에서 fbx까지 만들었다.

 

이제 콘텐츠 브라우저 창에 언리얼 엔진으로 드래그 해서 임포트를하면 

 

 

 

임포트 옵션이 뜨는데 나중에 마네킹과 함께 리타겟팅 해서 애니메이션을 동기화 할거니 모프타겟만 체크해주고

모두임포트를 누른다.

 

 

내가 원한결과는 당연히 이건데

 

 

이렇게 나올것이다.

 

당연히 fbx는 그저 메쉬에 텍스쳐만 매칭되어있을뿐 아무런 쉐이더 적용이 안되어있기 때문.

 

따라서 이 초기화되어버린 머테리얼들을 작업해줘야한다.

 

 

 

이 머티리얼들을 더블클릭해서.

 

노말맵 이미시브컬러 맵 디퓨즈 컬러는 자동적으로 설정되어있어 수고를 덜었다.

 

저 머리카락 텍스쳐에 색깔을 곱하면된다.

 

뭔말이냐면 

 

VRM임포트 만드신 갓발자님께서 툰쉐이더를 이쁘게 구현해주셨으니

 

그걸보고 참고해서 따라하거나 추가하고 싶은부분은 내가 표현하면되는것이다

 

빨간색부분을 중점으로 보면 

cutout 렌더링모드는 흑백 0~1(255)값에 따라 모서리를 주는 타입의 렌더링모드다.

 

불투명 쉐이더니 당연히 opaque로 되어있고.

 

이미션은 저 하얀부분은 빛에 영향받는것을 제외한다는 말이다. 즉 저 머리칼에 하얀부분만 써주겠다 하는 맵이다.

 

다음으로 옷을 해보자

 

 

 

블렌드모드를 마스크로해주고 옷 텍스쳐의 알파값 핀을 오파시티 마스크로 끌어다 주면 

저 검은색부분은 제외되고 렌더링 한다.

 

나머지 속눈썹 부분도 이런식으로 매터리얼 전부 편집해주면 된다.

 

 

속눈썹 색깔도 머리 해주듯이 해주면 된다.

 

이작업을 다하면 대충 맨 첫그림과 비슷하게 나온다.

 

 

unlit이라 하면 unlighting을 줄인말인데 

카툰렌더링이 언릿을 기본으로 깔고있으며

빛이 들어오는 부분의 내적연산을 단계적으로 조절해서 그림자를 표현한다. 

 

라이팅 제외를 하니까 그럴싸 하게 나오지만 게임에 라이팅을 안쓸리가 없으니 

저것을 셀쉐이더를 입혀주도록하고

 

vrm쉐이더 코드를 뜯어서 최대한 비슷하게 만들어보도록 하겠다.

 

다음 포스팅에는 언리얼엔진에서 과도하게 뽀샤시하게 나오는 포스트 프로세싱을 꺼주고

톤맵퍼에 대해 알아보자.

https://www.udemy.com/share/100YVuA0sbd1tXRng=/

 

이거보고 공부했읍니다.

가격도 매우 저렴합니다 언리얼 블로그에서 링크통해가면 더 할인도 되요(영어주의)

 

 

 

리플리케이트로 움직이는 액터의 

 

 

헤더파일

 

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

#pragma once

#include "CoreMinimal.h"
#include "Engine/StaticMeshActor.h"
#include "MovingPlatform.generated.h"

/**
 * 
 */
UCLASS()
class TEST_API AMovingPlatform : public AStaticMeshActor
{
	GENERATED_BODY()

public:
	AMovingPlatform();
	
	virtual void Tick(float DeltaTime) override;

	virtual void BeginPlay() override;

	UPROPERTY(EditAnywhere)//블프나 에디터상에서 모두 편집가능
		float Speed = 20;

	UPROPERTY(EditAnywhere, Meta = (MakeEditWidget = true))//에디터상 위젯에 나타냄
		FVector TargetLocation;

	void AddActiveTrigger(); //이동
	void RemoveActiveTrigger(); //해제

private:
	FVector GlobalTargetLocation;
	FVector GlobalStartLocation;

	UPROPERTY(EditAnywhere)
		int ActiveTriggers = 0; //트리거 발동조건
};

 

구현부

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

#include "MovingPlatform.h"

//움직이는 스태틱메쉬
AMovingPlatform::AMovingPlatform() {
	//틱 이벤트 사용
	PrimaryActorTick.bCanEverTick = true;

	//이물체가 이동성을 가지는지 설정 Enum형
	SetMobility(EComponentMobility::Movable);

}

void AMovingPlatform::BeginPlay()
{
	Super::BeginPlay();

	if (HasAuthority()) {
		//서버와 클라이언트를 동기화
		SetReplicates(true);
		SetReplicateMovement(true);
	}
	GlobalStartLocation = GetActorLocation();
	GlobalTargetLocation = GetTransform().TransformPosition(TargetLocation);
}

void  AMovingPlatform::Tick(float DeltaTime) {
	//부모로부터 가상함수를 재정의 하고있는지 확인
	Super::Tick(DeltaTime);

	if(ActiveTriggers >0 ) //트리거 발동되었을때.

	//참이면 서버, 거짓이면 단일 클라이언트일때를 분기
	if (HasAuthority()){
		//현재액터의 위치 변수저장하기
		FVector location = GetActorLocation();
	//틱마다 x축으로 델타시간*스피드 만큼 이동

	float JourneyLength = (GlobalTargetLocation - GlobalStartLocation).Size(); //목표지점과 첫지점의 거리
	float JourneyTravelled = (location - GlobalStartLocation).Size(); //현재내지점과 목표지점의 거리 

	if (JourneyTravelled >= JourneyLength) //목표지점 첫지점을 바꿈 
	{
		FVector Swap = GlobalStartLocation;
		GlobalStartLocation = GlobalTargetLocation;
		GlobalTargetLocation = Swap;
	}

	//방향벡터구하기
	FVector Direction = (GlobalTargetLocation - GlobalStartLocation).GetSafeNormal();
	//이동
	location += Speed * DeltaTime * Direction;
	SetActorLocation(location);
	
	
	
}
	
}

void AMovingPlatform::AddActiveTrigger()
{
	ActiveTriggers++;
}

void AMovingPlatform::RemoveActiveTrigger()
{
	if (ActiveTriggers > 0) {
		ActiveTriggers--;
	}
}

 

 

버튼이눌렸을때 액터가 움직이도록  하는 액터의 헤더파일 

 

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PlatformTrigger.generated.h"

UCLASS()
class TEST_API APlatformTrigger : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	APlatformTrigger();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

private:
	UPROPERTY(VisibleAnywhere)
		class UBoxComponent* TriggerVolume;


	UFUNCTION() // 겹쳤을때 함수
		void OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

	UFUNCTION() // 나왔을대 함수
		void OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

	UPROPERTY(EditAnywhere) //트리거가 발동되었을때 움직이는 플랫폼의 배열 
		TArray<class AMovingPlatform*> PlatformsToTrigger;

};

 

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

#include "PlatformTrigger.h"
#include "Components/BoxComponent.h"
#include "MovingPlatform.h"

// Sets default values
APlatformTrigger::APlatformTrigger()
{
 	// 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;

	TriggerVolume = CreateDefaultSubobject<UBoxComponent>(FName("TriggerVolume"));
	if(!ensure(TriggerVolume != nullptr)) return;
		RootComponent = TriggerVolume;

		TriggerVolume->OnComponentBeginOverlap.AddDynamic(this, &APlatformTrigger::OnOverlapBegin);
		TriggerVolume->OnComponentEndOverlap.AddDynamic(this, &APlatformTrigger::OnOverlapEnd);
}

// Called when the game starts or when spawned
void APlatformTrigger::BeginPlay()
{
	Super::BeginPlay();
	
}

// Called every frame
void APlatformTrigger::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void APlatformTrigger::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
	UE_LOG(LogTemp, Warning, TEXT("Activated"));

	for (AMovingPlatform* Platform : PlatformsToTrigger) //배열에있는 트리거들 모두순회
	{//이동트리거발동
		Platform->AddActiveTrigger();
	}

}

void APlatformTrigger::OnOverlapEnd(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
	UE_LOG(LogTemp, Warning, TEXT("Deactivated"));

	
	for (AMovingPlatform* Platform : PlatformsToTrigger)
	{//이동트리거제거
		Platform->RemoveActiveTrigger();
	}
}

 

 

내가 3D맥스를 공부한 이유를 무쓸모로 만들어버린

 

Vroid를 유니티로 만들었다고 유니티에서만 써야한다는건 좀 뭔가 불합리해보여서

 

VR장비만으로 Vtuber 만드는김에 언리얼엔진에서도 써보고싶었다.

 

나온지 오래되지않은거라 영어,일본어,중국어로 구글링해봤는데 안나와서 직접 삽질하기로 결정!

 

vroid로 모델링을 만들어서 익스포트하면 

vrm확장자로 나온다.

 

이걸 fbx로 만들고싶다! 해서

 

시도한 첫번째방법.

이걸 gbl파일로 이름바꾸기하고 윈도우3D에서 fbx파일로 다른이름으로 저장!

텍스쳐 다날라가서 흰색 지점토만 나온다 ^오^

 

그래서 혼자 별짓다해서 드디어 방법을 찾아냄.

 

준비물.

 

VRoid

 

https://studio.vroid.com/download.html

 

VRoid

VRoid Studioは、人型アバター(キャラクター)の3Dモデルを作成できるWindows・Mac用アプリケーションで、どなたでも無償で利用可能になります。

studio.vroid.com

미소녀를 엄청나게 빠르게 모델링가능한 갓갓툴이다..

그러나 하이폴리곤 모델링이니 여러모델을 동시에 쓰기는 좀 힘들다.

전문 모델러가 리토폴로지하던가 머리카락 본도 손보고 등등 해야할게 좀 있을것이다.

그래도 버텍스가 많은만큼 이쁘게 나오고 쉐이더 공부하기 엄청조흠 굿굿.

 

 

 

https://github.com/dwango/UniVRM/releases

 

dwango/UniVRM

Unity package that can import and export VRM format - dwango/UniVRM

github.com

vrm을 유니티로 임포트 시켜주는 플러그인을 다운받는다.

 

 

그리고 에셋스토어에서 fbx익스포터를 다운받는다.

 

유니티 익숙한사람은 이쯤되면 감 올것이다.

 

자 이렇게 준비물 갖고오면 

 

유니티에 이렇게 보인다.

VRM탭에서 

import를 찾아 vrm파일을 임포트하면

요로케 프리팹으로 만들어준다.

 

 

유니티용답게 이쁘게 나오고 프레넬효과로 뽀샤시한 쉐이더가 적용되어있다.

 

스킨드메쉐 렌더러로 모퍼도 잘 임포트 되었다.

 

이제 지옥이 시작될것이니라

 

이 오브젝트를 하이어라키에서 오른클릭해서 fbx 익스포트를 한다.

 

스킨드메쉬 옵션 꺼져있는데 저거 체크해야한다.

 

나머지 옵션들 그대로 익스포트 ㄱ

 

그러면 스탠다드 쉐이더가 적용된 FBX의 몬슁긴 모델링이 나온다.

 

내가 맥스 배우고 저꼴나서 쉐이더 공부하기 시작했다.

 

이제 유니티 경로에있는 이 모델을 잘보이는 바탕화면이든 아무데나 복사해서 옮긴다!

이득우 교수님께서 IGC에서 발표해주신 언리얼 에디터 확장하기를 메인으로 공부 시작.

http://blog.naver.com/PostView.nhn?blogId=destiny9720&logNo=221381098226&categoryNo=21&parentCategoryNo=&from=thumbnailList

 

 

언리얼 위키

https://wiki.unrealengine.com/Creating_an_Editor_Module#Module_Folder_Structure

 

일본어 블로그

http://historia.co.jp/archives/370/

 

 

 

내가 이걸 정리하는 이유는 처음봤을때 자료를보며 누락된 내용이나 배경지식의 부족 때문에

삽질을 오지게해서다.

 

 

자 여기서 흔히 언리얼에서 코딩하는건 런타임모듈인데.

저기 에디터 모듈이라는거 따로 만들어줘야한다.

난 이거 언리얼에서 자동으로 만들어주는줄 알고 코드만 따라 쳐봤다가ㅋㅋ

왜 에러나지 하면서 하루종일 MSDN 쳐다보고있었다. ㅡㅡ

 

첫번째로 할일은 uproject파일을 메모장으로열면

프로젝트의 정보가 json파일 형태로 나타난다.

 

요걸

이렇게 모듈이름을 정해주도 타입을 에디터로 해서 추가해준다.

 

 

대표

사진 설명을 입력하세요.

 

저 폴더를 만들고

extensionEditor.target.cs 소스를

 

 

using UnrealBuildTool;
using System.Collections.Generic;

public class extensionEditorTarget : TargetRules
{
	public extensionEditorTarget(TargetInfo Target) : base(Target)
	{
		Type = TargetType.Editor;

		ExtraModuleNames.AddRange( new string[] { "extension", "extensionEditor" } );
	}
}

 

코드처럼 에디터 모듈을 코딩해준다.

 

 

일단 모듈을 만드려면 이렇게

소스폴더 안에 모듈폴더를 만들고

그 안에 폴더이름과 같은 파일들을 만들어줘야 하는데 내용물도 비슷해야 하므로

아예 새로만들면 같은 코딩을 두번해야하는 귀찮음이 있으니

프로젝트 만들면 있는 소스들을 복붙한다음 파일 이름이랑 안에 코드들만 바꿔주면 편하게만들 수 있다.

 

EditorStudy.build.cs 파일을 보고 의미를 찾아봤다.

 

 

이외에 API목록은 여기 찾아보면 있음 http://api.unrealengine.com/INT/API/

 

이제 헤더파일을 뜯어보자.

#pragma once

#include "CoreMinimal.h"


// 
class FExtensionModule : public IModuleInterface
{
public:
	// IModuleInterface 인터페이스 상속을 받으며 이 모듈이 로드,언로드 될때 호출할 가상함수 선언
	/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;
//이 두개는 cpp에서 무조건 구현해야함 안하면 에러뱉음
private:
	//FUIcommandlist 클래스는 에디터상의 멀티박스와 매핑해주는 클래스.
	TSharedPtr<class FUICommandList> CommandList;
};

열심히 구글링했는데 아직은 먼말인지 정확히 이해는 안되나 그런 인터페이스라고 대충 알아먹어두자.

 

 

그리고 모듈의 구현부

 

#include "extensionEditor.h"
#include "Modules/ModuleManager.h"

//멀티박스 추가에 필요한 헤더
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "LevelEditor.h"


// IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, extension, "extension" );
//Primary게임모듈은 한프로젝트에 하나만 있어야한다는 규칙이다.
//지금 만드는건 에디터용 보조모듈이니 임플리먼트 모듈로 만든다.
IMPLEMENT_MODULE(FextensionEditor, "ExtensionEditor");


#define LOCTEXT_NAMESPACE "Menu"

void FextensionEditor::StartupModule(){
}

void FextensionEditor::ShutdownModule() {

}

#undef LOCTEXT_NAMESPACE

 

이렇게 만들고 솔루션을 다시 빌드한 후 .

 

언리얼 에디터를 켜서러 새 c++ 클래스를 만들면.

 

그리고 모듈제작을 했으니 에디터 모듈이 제작가능해진다!

 

내가 이 모듈을 사용할때 어떤 명령을 내리고 어떤 동작을 실행할건지 또 정해줘야한다.

 

https://api.unrealengine.com/INT/API/Runtime/Slate/Framework/Commands/TCommands/index.html

TCommands

A base class for a set of commands.

api.unrealengine.com

 

https://api.unrealengine.com/INT/API/Runtime/Slate/Framework/Commands/FUICommandInfo/__ctor/index.html

FUICommandInfo::FUICommandInfo

Creates and initializes a new instance.

api.unrealengine.com

 

위 링크는 언리얼 에디터 내에 UI정보를 담고있는 커맨드에 대한 설명

 

 

다음글에서

 

인터페이스 상속받아서 구현해야하는 

StartupModule 과 
ShutdownModule를 

 

이제 내가 원하는 대로 코딩해보자

+ Recent posts