랜더텍스처

 

Render Texture는 런타임에서 생성 및 갱신되는 특수한 Texture입니다. 사용하려면 먼저 새 렌더 텍스처를 만들고 Cameras 중 하나를 지정하여 거기에 렌더링합니다. 이제 정상적인 텍스처처럼 Material의 렌더 텍스처를 사용할 수 있습니다. Unity 표준 에셋의 Water 프리팹은 실시간 반사와 굴절을 생성하기 위해 렌더 텍스처를 실제 월드상에 사용하는 예입니다.

<https://docs.unity3d.com/kr/530/Manual/class-RenderTexture.html>

 

 

카메라가 비추는 화면을 다른 특정 이미지에서 보여줄 때 사용했다. 

 

1. 랜더텍스처 생성

랜더 텍스처를 생성한다.

2. 카메라 추가

일단 특정 오브젝트를 비추는 카메라가 있어야 한다. 그렇기에 카메라를 추가하고 

이곳에 아까 생성한 랜더 텍스처를 추가한다.

3. Raw 이미지 추가

Raw 이미지를 추가한다. 이 이미지 크기로 카메가 비추는 화면이 보이게 된다. 적절히 크기를 설정해주고

이쪽에도 랜더 텍스처를 넣어준다.

4. 추가 작업

 

보이긴 하는데 뒷배경도 나와 보기가 별로다. CullingMask를 통해 보일 오브젝트만 레이어로 분류하면 된다.

나 같은 경우는 ClearFlag를 Solid로 바꿔서 캐릭터만 보이도록 했다.

 

 

 

 

 

코드 분석 중에 모르는 것이 나와서 정리해두기 위해 포스팅했다.

Destroy

오브젝트(Component,Asset)를 삭제시켜주는 함수다. 가비지가 많이 생성시켜서 반복적으로 불리는 경우(몬스터 등)에서는 사용하지 않고

특정 시간을 주어서 삭제 시간을 정할 수도 있다. Destroy (obj : Object, t : float = 0.0F) 실제 객체 파괴는 항상 현재 Update 루프가 끝나기 전까지 지연되며 렌더링이 되기 전에 파괴된다고 한다.

DestroyImmediate

Destroy 이와 같은데 지연시간을 줄 수가 없다. 큰 차이점은 Update루프가 끝난 후가 아닌 바로 삭제(동일한 프레)된다는 점과 편집기 코드를 작성할 때 사용하고 게임 코드에서는 Destroy를 이용하라고 한다.  주의점으로는 영구적으로 파괴할 수 있다는데..) 

 

언제 DestroyImmediate를 사용해야 하는지는 조금 더 알아봐야겠다.

 

 

https://docs.unity3d.com/ScriptReference/Object.DestroyImmediate.html

NGUI를 공부하다가  간단하게 뭐라도 만들면 좋을것 같다고 생각하다가 예~전에 윈도우에 설치되어있던 카드게임을 만들게 되었다. 솔리테어인데 사실 이 게임 룰도 몰랐다. 생각보다 만들다 보니 룰이 복잡했다. 아직 카드를 놓을 수 있는 룰 제한은 넣지 않았다. 

1.카드 나열

순서대로 1번째는 1장,2번째는 2장 ~~~7번째는 7장의 카드가 놓여지고 그 배열의 맨 마지막 카드만 뒤집어짐

2. 나열되지 않은 카드배열 뒤집기

밑에 나열된 카드를 제외하고 남은 카드배열을 가지고있다가 버튼을 누르면 위에서부터 하나씩 뒤집어준다.

3. 마지막 위치한 카드 뒤집기 (아직 룰 적용 X)

카드를 선택하고 이동할 위치에 클릭하면 카드가 선택한 카드의 위치의 카드정렬은 마지막카드를 뒤집어준다.

미처리 부분 

- A로 적혀있는 빈공간에는 A부터 차례대로 놓을 수 있게 처리

- 밑에 뒤집어져있는 7개의 카드에는 제일 위에있는 카드 숫자보다 1 큰 카드만 올 수있고 다른 색의 카드가 와야한다 ex) 7클로버(검      은색)카드가 있다면 8하트(빨간색)or8다이아(빨간색)..빨간색의 숫자가 8인 카드

- 맨 오른쪽위의 카드는 7개의 깔려있는 카드 외의 카드들이다. 모두 뒤집었을 경우 다시 반대로 뒤집어 지도록 

- 모든 카드들은 클릭될 수가 있으므로, 뒤집어져 있는 카드만 클릭이 되도록 설정 예를들어 5장의 카드가 뒤집어져있는 카드배열을 클릭하면 모두 움직이도록 설정이 필요

- 추가적으로  생각나지 않는것들..

간단하게 연습삼아 만들어봤는데 생각보다 복잡해서 당황했다..

 

'유니티 > NGUI' 카테고리의 다른 글

NGUI) UI 사용법[버튼 클릭처리,텍스트(Label)]  (0) 2019.04.30
NGUI) Sprite,아틀라스 활용하기  (0) 2019.04.28

Label 은 폰트를 설정하여 글씨를 표시할 수 있는 객체

Texture 는 PNG, JPG 등의 파일을 불러와 이미지를 표시

Sprite 는 PSD 등의 Atlas파일을 불러와 이미지를 표시하는 오브젝트

Widget 은 기본적인 공간 상의 설정만을 가지는 빈 오브젝트

 

일단 작업전에 셋팅해주면 좋은거

원활한 UI작업을 위해 기즈모 사이즈를 줄여서하는 좋을 듯하다.

NGUI를 사용하려면 알아야 할 기본적인 것들

1. 모든 UI들은 UI Root안에 있어야한다. (UGUI의 canvas처럼)

2. 보통 UGUI와 NGUI를 같이 사용하지 않는다.

3. Depth기준을 어느정도 정해놓고 작업 진행하는게 좋다.

3. UI Panel Clipping -> softclip으로 변경 시

 

버튼 기능만들기

UGUI는 Button컴포넌트가 있다면 NGUI는 EventTrigger가 있다

1. 셋팅

UGUI버튼의 OnClickButton이랑 비슷한 기능이다. On Press는 마우스 클릭했을 때 발동된다. 

클릭하면 스프라이트이미지가 변경된다.

BoxCollider 추가하기

마우스처리를 하려면 콜라이더를 추가해야한다. 그리고 Collider auto-adjust to macth를 클릭해준다. 이게 무엇이냐 NGUI장점 스프라이트 크기만큼 콜라이더의 사이즈가 맞춰진다. 굿!

UI Camera 셋팅 

2D UI로 변경해준다.

텍스트 만들기 Label

Label을 처음 만들면 font를 지정해줘야한다. 같은 객체 안에 UI sprite, UI Label을 같이 사용하지 않도록한다.

확실히 다른 컴포넌트를 추가하지 않아도 기본적인 NGUI 기능들이 많다. 다 쓸까? 오히려 복잡해보이기도 하고 빌트인된 UGUI가 아직 더 편한거 같다.

'유니티 > NGUI' 카테고리의 다른 글

NGUI연습) 솔리테어 카드게임_작업중  (1) 2019.05.03
NGUI) Sprite,아틀라스 활용하기  (0) 2019.04.28
	public UISprite sprite;
    void Start()
    {
		sprite.spriteName = "key4";
	}

UGUI에서 아틀라스 공부했을때 드로우콜을 줄여 성능에 도움이된다고 학습했다. NGUI에서도 아틀라스를 다루기위해 학습 포스팅하게 되었다.

(UGUI아틀라스) : https://funfunhanblog.tistory.com/44

UGUI와 사용 방법이 다르다.

1.아틀라스 생성

Create -> Atlas

이름을 지정하고 생성해준다.

성공적으로 생성이 됐다면 Assets폴더에 생긴다.

 2. sprite추가

Open -> Atlas Maker

Atlas Maker를 누르면 위 그림과 같은 창이 뜨는데, 1번 그림 처럼 Atlas를 누르면 2번 그림 처럼 새로운 창이 뜬다. 그러면 현재 만들어져있는 아틀라스 목록들이 나오는데 여기서 사용할 아틀라스를 클릭해준다.

이미지를 추가할 아틀라스를 선택하고 이미지를 클릭하면 자동으로 sprite목록을 추가된다.  그 다음 Add/Update를 누르면 실제 아틀라스에 묶이게 된다.

삭제
추가

삭제한 다음에 이미지를 클릭하면 Add로 상태 보여준다. 같은 방법으로 Add/Update 버튼을 눌러준다.

3. sprite적용방법

sprite객체를 만들고

UI Sprite스크립트에서 생성한 아틀라스와 스프라이트를 설정해준다.

3. sprite적용방법2_스크립트적용

spritename = coin3

    public UISprite sprite;
    void Start()
    {
		sprite.spriteName = "key4";
	}

sprite.sprtieName = "이미지 이름"; 이러면 끝이다

ugui보다 간편하게 적용이 가능하다.

 

유니티) 포물선 궤적그리기


포탄을 날리거나 포탄 궤적을 보여주기 위해 사용된다.

유니티에서는 Line Renderer을 이용해봤다.



나는 포탄궤적이 아닌 캐릭터가 날아갈 점프 궤적을 그리는데 이용했다.


스크립트


셋팅


playerPlane :  라인이 그려질 plane(땅)의 위치를 셋팅해준다. 궤적이 시작하는 위치


마우스로 캐릭터 방향바꾸기 글에서 학습한적이 있었다.

https://funfunhanblog.tistory.com/40 (평면을 결정하는 최소 조건)


targetPoint는 포물선이의 끝점이된다. (내 게임에서는 노란색 circle에서 최종 위치를 받아온다)


체크



Raycast를 통해 마우스가 위치하는 곳을 가져온다. 

center : 시작벡터와 착지위치벡터의 합에 1/2은 위치가 포물선의 중간위치가 된다.

targetRotation : 라인위치와 최종위치를 빼면 포물선의 방향백터를 구할 수 있다. 

(벡터OA -벡터 OB = 벡터BA) center가 아닌 캐릭터의 위치로 계산해도됨)

Physics.Linecast : 포물을 그리는 라인에 물체가 걸리면 부딪힌 지점을 넘겨준다.



실제로 궤적(라인)을 그리는 부분




theArc : 두 벡터 사이를 원하는 간격으로 보간 부분

(이해를 못함.. 이쪽은 공부가 필요하다)

lineRenderer.SetPosition : 위에서 구해준 라인의 벡터들의 위치를 설정



학습참고 : http://maedoop.dothome.co.kr/660









구글독스 데이터 저장



인게임 데이터를 구글폼과 연동해 저장하기


로그인시스템과(실제 게임과 로그인은 많이 다르 지만) 스코어를 저장시스템을 만들기



1. 구글독스 설정

데이터를 저장할 장소와 입력을 받을 매체를 설정하는 부분이다.


1). 구글설문지

이 구글 설문지가 유니티에서 데이터 입력매체가 되는 것이다.


저장할 데이터 항목 맞게 질문을 추가해준다 (단답형으로 변경)  



질문항목을 다 채웠다면 '응답'탭을 누르고 스프레드 시트를 만들어준다. 


2). 구글스프레드시트

스프레드시트를 열어보면 자동적으로 질문항목들이 채워져있다.

이 부분이 설문지로 받은 입력데이터들이 저장 될 곳이다. 


2. 스크립트 부분


유니티 스크립트에서 데이터를 입력매체(설문지)를 연결하는 방법



SAVE_URL : 데이터폼의 키 값이다. 


설문지에서 '미리보기' 클릭 후 페이지소스 보기

form action = 이 부분이다.


entry. : 스프레드 시트 각각 열의 항목



만약 질문 항목이 5개라면 entry 값도 5개 인 것이다





실행 테스트 


5가지 항목을 추가 했다.

nID : 로그인 정보가 입력된 번호(순서)

sID : 아이디

sPassword : 패스워드

sDeviceID : 기기 아이디

eOS : 운영체제 (안드로이드,IOS,PC)


이 부분은 디바이스 아이디를 받아오고, OS정보를 파악 


유니티 INPUT.TEXT를 통해 아이디와 패스워드를 입력한다.



결과

타임스탬프는 설문지를 통해 스프레드 시트를 만들면 자동으로 생성되는 항목인데 데이터를 입력한 시간을 입력해준다.


이 부분은 데이터를 입력하는 부분만 있다. 

로그인이라면 입력한 아이디와 패스워드가 맞는지 체크하는 기능이 있어야한다.  

그럴라면 데이터를 스프레드시트에서 받아오는 기능이 필요하다.

기초 조명셰이더 난반사광





(게임속에서 직접광은 계산하지만 간접광은 수없이 

반사의 반사를 거치므로 표현하기 어렵다고 한다.)


간접광 : 반사되는 빛

직접광 : 광원으로 직접 받는 빛



난 반사광(Diffuse Light)


우리가 물체를 볼 수 있는 이유는 다른 물체가 비추는 빛이 이 물체를 표면에 반사되기 때문에 볼 수 있는 것이다. 이 것이 난 반사광이라고 한다.


물체를 어느 방향에서 보든 명암이나 색조가 크게 변해 보이지 않는 이유가 여러 방향으로 고르게 퍼지는 난 반사광 덕분이다.


(그림 : https://kblog.popekim.com/2011/12/04-part-1.html)


수학적으로 난 반사광을 어떻게 계산하려면?


lambert모델(게임에서 주로 사용됨)을 살펴보면 

입사광이 이루는 각의 코사인 값을 구하면 난 반사광의 양을 구 할 수 있다.


입사광과 법선의 두개의 각도에 따라 코사인 값은 아래와 같다.

0도 => 1

90도 => 0

90도를 넘는 순가 음수 값을 갖다가

180도에서 -1

음수 값은 아예 깜깜해지기 때문에 값 변경이 필요하다.


 

코사인값을 구하는 수학 공식을 보면

cosθ = (A ∙ B) ÷ (| A |ⅹ| B |);

두 개의 내적을 구한 뒤 두 벡터의 길이를 곱한 결과를 나눈 것과 같다. 


여기서 두 벡터를 1로 정규화한다. (두 벡터가 이루는 각이 중요할뿐 길이는 중요하지 않기 때문에) 

cosθ = (A'  B')


두 벡터가 이루는 각의 코사인 값은 두 벡터의 내적과 같다는 의미이다.


셰이더에서 난 반사광을 계산하려면?


물체 표면 벡터는 정점에 저장되어 있기 때문에 그대로 가져오면 되고 입사광의 벡터는 광원의 위치에서

현재 픽셀 위치까지 긋고 그 그것의 벡터를 가져와야한다.


선을 긋는 것을 벡터의 뺄셈이라고 하는데, 모든 변수의 공간을 일치 시킨 뒤 현재 위치에서 광원의 위치를 빼면 입사광의 벡터를 구할 수 있다. (표면의 법선도 같은 공간에 있어야 함)


난 반사광 범위는 0~1이니까 위에서 코사인값을 0이하의 값은 0으로 1이상의 값을 1로 맞춰 줘야한다.




학습참고 출처 https://kblog.popekim.com/2011/12/04-part-1.html

하프 램버트



램버트 라이트는 cos그래프 연산의 특성상 밝다가 너무 갑자기 검게 음영이 떨어지는 단점이 있다.

이 단점을 해결하고자 만든것이 하프 램버트이다.



공식

Dot(L,N) *0.5 + 0.5

램버트에 0.5를 곱해 평준화 시킨 다음에 0.5를 더해서 x축으로 들어 올려준다.

즉 -1에서부터 1까지의 숫자를 0에서부터 1까지의 범위로 만듦으로서 좀 더 부드럽게 바꿔준다.


float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten)

{

float4 ndot1 = dot(s.Normal, lightDir) *0.5 + 0.5;

return ndot1;

}



오른쪽 하프램버트 공식을 적용한 모델



부드러워졌지만 이 음영은 너무 부드러워서 물리적으로 전혀 옳지 않다.

명이 너무 넓고 암이 너무 좁다. (음영 콘트라스트가 거의 없음 콘트라스트:~대비)



해결방법


하프 램버트를 몇번 제곱해서 콘트라스트를 올려준다.


float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten)

{

float4 ndot1 = dot(s.Normal, lightDir) *0.5 + 0.5;

return pow(ndot1,4);

}

pow 함수 : pow(X,n) -> X의 n제곱





위 쉐이더는 빛 방향에 따른 밝기만 구현되어있음


이번에는 조명의 색상과 강도, 빛 감쇠, 텍스처까지 적용 보려한다.



float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten)

{

float4 ndot1 = saturate(dot(s.Normal, lightDir));

float4 final;

final.rgb = ndot1 * s.Albedo * _LightColor0.rgb* atten;

final.a = s.Alpha;


return final;

}


return 값이 final로 바뀌었다. 


s.Albedo : 램버트 라이트와 연산되어 Diffuse가 된다.

_LightColor0.rgb : 조명의 색상이나 강도를 가져옴

atten : 빛의 감쇠 현상을 시뮬레이트한다.

final.rgb :  이전에 연산했던 조명과 노멀의 각도인 ndot1연산과 Albedo , _LightColor0.rgb를 곱해 변화를 적용한다.

 

atten은 자기 자신의 그림자를 만들거나, 다른 물체에 때문에 생기는 그림자, 조명의 감쇠 현상(멀어지면 어두워지는 효과)때문에 어두운 부분 이 있다.




학습참고

1) 유니티쉐이더 스타트업 도서

2) https://www.slideshare.net/tartist/ss-56389186


램버트 라이트


커스텀 라이트로 램버트 라이트 구현하기


알아 두어야 할 것


법선 벡터가 조명벡터를 마주볼 때 해당 버텍스는 가장 밝다.


두 벡터의 각도의 따른 내적 값 

0도이면 =>1

90도이면 =>0

180도이면 => -1






램버트 라이트란 


밸브에서 개발했으며 하프라이프에서 처음 사용되었다고 한다.

주변 및 방향 조명을 통합하여 3D장면의 개체를 음영 처리한다.


공식 Dot(L,N) L:라이트벡터, N:법선 벡터(노말 벡터)

법선 벡터가 곱해지면 표면에 굴곡진 라이팅을 생성하게 해주는 방법



float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten)

{

float4 ndot1 = dot(s.Normal, lightDir);

return ndot1;

}

dot 함수 : 노멀벡터와 라이트 벡터를 내적 연산


내적 연산한 값은 1~ -1까지 범위의 숫자가 나온다. 

즉, 그림에서 가장 밝은 곳은1, 어두워지는 경계선은 0 아예 어두운 곳은 -1일 것이다.



-1인 부분은 너무 어둡기 때문에 변경이 필요하다.


float4 LightingTest(SurfaceOutput s, float3 lightDir, float atten)

{

float4 ndot1 = saturate(dot(s.Normal, lightDir));

return ndot1+0.5;

} 

saturate 함수 : 이 안의 값을 0~1사이로 잘라줌 0보다 작은 값은 0으로 1보다 큰값은 1로

+ 차이를 위해 결과 값에 +0.5를 해준다.



(NormalMap 추가했음)



(아까 더했던 0.5를 지우고)




학습참고 : 

1) https://www.slideshare.net/tartist/ss-56389186

2) 유니티쉐이더 스타트업 도서


전처리기 / Define


유니티 Define 기능은 각 플랫폼 또는 특정 버전에 코드를 처리할때 유용한 방법이다.



전처리기


멀티플랫폼이 가능한 유니티다 이 말은 하나의 프로젝트로 여러가지 플랫폼으로 빌드가 가능하다는 말이다. 멀티플랫폼이라서 그렇게 간단하게 각기 다른 플래폼 적용이 쉬운것은 아니다. 


안드로이드,IOS,윈도우 각각 플랫폼마다 적용해야 하는 코드가 다를 수 있다. 그러면 플랫폼이 바뀔때마다 스크립트를 각각 별도로 만들어야하고 동적으로 플랫폼을 확인한 후에 컴포넌트를 추가해야 한다.

프로젝트가 커질 수록 작업양은 점점 많아 질 것이다.  



이럴 때 사용하는 것이 전처리기이다.


구문

#if UNITY_EDITOR

Debug.Log("Unity Editor");

#endif


예제)


유니티 에디터에서 각각 다른 플랫폼에서 다른 로그가 나오도록 설정할 수 있는 것이다. 


<유니티에서 지정해놓은 전처리기>



사용자 커스텀 전처리기

유니티가 지정해 놓은 전처리기 말고 사용자가 원하는 전처리기를 만들 수 가 있다.

1.UnityEditor에서 사용자 디파인 셋팅


edit -> Player Settings -> Other Setting -> Scripting Define Symbols




정의하고 싶은 특정 플랫폼의 기호 이름을 입력한다.



Player Settings에는 SHOW_DEBUG_MESSAGES 설정을 했으니


"디파인 테스트2"만 출력된다.






2. using UnityEditor; 필요



특징 컴파일 시 Define여부에 따라 컴파일 자체를 하지 않기 때문에, 실행파일에 코드가 포함되지 않는다.

Define으로 인해 유니티에서 현재 선택되어진 플랫폼이 아닌 다른 플랫폼의 코드들의 스킙트 에러 여부를 바로 확인할 수 가 있다.




학습참고

1) https://docs.unity3d.com/kr/530/Manual/PlatformDependentCompilation.html

2) http://blog.naver.com/PostView.nhn?blogId=hope0510&logNo=220079637714

3) https://bluemeta.tistory.com/12


서피스 툰 셰이더 #1

 

<툰 쉐이더 적용 전>


We're gonna set a custom lighting function

1) 우리가 설정 가능한 조명기능을 설정해주도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Shader "Custom/ToonShaderPratice"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
 
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0
 
        sampler2D _MainTex;
        half4 _Color;
 
        half4 LightingToonLighting(SurfaceOutput s, half3 lightDir, half atten) 
        {
 
        }
        struct Input
        {
            float2 uv_MainTex;
        };
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Shader "Custom/ToonShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf ToonLighting
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
        sampler2D _MainTex;
        half4 _Color;
 
        half4 LightingToonLighting(SurfaceOutput s, half3 lightDir ,half atten)
        {
            float NdotL = saturate(dot(s.Normal, lightDir))* atten;
            float toonL = step(0.5, NdotL);
            return half4(toonL,toonL,toonL,1);
 
        }
        
        struct Input
        {
            float2 uv_MainTex;
        };
 
 
        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex)* _Color;
            //o.Albedo = c.rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Shader "Custom/ToonShader"
{
    Properties
    {
        _LitOffset("Lit Offset",Range(0,1))=0.25
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf ToonLighting
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
        sampler2D _MainTex;
        half4 _Color;
        half _LitOffset;
 
        half4 LightingToonLighting(SurfaceOutput s, half3 lightDir ,half atten)
        {
            float NdotL = saturate(dot(s.Normal, lightDir))* atten;
            float toonL = step(_LitOffset, NdotL);
            return half4(s.Albedo * toonL,1);
 
        }
        
        struct Input
        {
            float2 uv_MainTex;
        };
 
 
        void surf(Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex)* _Color;
            o.Albedo = c.rgb;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs


우리의 음영을 향상시키기 위해 우리는 표면에서 완전히 어두운 부분을 원하지 않는다.

_SSColor 은 "피부" 아래 인물의 색감이라고 생각하다. 그리고 그늘진 부분에서 우리는 알베도를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
Shader "Custom/ToonShader"
{
    Properties
    {
        _LitOffset("Lit Offset",Range(0,1))=0.25
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _SSSTex("SSS Map",2D) = "black" {}
        _SSSColor("SSS Tint",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf ToonLighting
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
        sampler2D _MainTex;
        sampler2D _SSSTex;
        half4 _Color;
        half _LitOffset;
        half _SSSColor;
 
        struct CustomSurfaceOutput {
            half3 Albedo;
            half3 Normal;
            half3 Emission;
            half Alpha;
            half3 SSS;
        };
 
        half4 LightingToonLighting(CustomSurfaceOutput s, half3 lightDir ,half atten)
        {
            float NdotL = saturate(dot(s.Normal, lightDir))* atten;
            float toonL = step(_LitOffset, NdotL);
            half3 albedoColor = lerp(s.Albedo * s.SSS, s.Albedo *toonL, toonL);
            return half4(albedoColor,1);
 
        }
        
        struct Input
        {
            float2 uv_MainTex;
        };
 
 
        void surf(Input IN, inout CustomSurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex)* _Color;
            o.Albedo = c.rgb;
            o.SSS = tex2D(_SSSTex, IN.uv_MainTex) * _SSSColor;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
 
cs

당신이 보는 것처럼 우리는 우리가 질감으로 더럽힐 수 있는 그림자에 단정한 어두운 색을 가지고 있다.

그러나 문제는 있다 .만약 우리가 라이트 색을 바꾼다면 그것은 머테리얼에 영향을 미치지 않을 것이다.

간단한 해결책: 단지 밝은 색과 우리의 밝은 알베도를 곱하면 된다.




next problem : look at that It's totally flat even when it should have shadows between the plates

다음 문제: 저것 좀 봐. 접시 사이에 그림자가 있어야 하는데도 완전히 평평해.



We'll paint the vertices of our model to mark the should shaded parts and read that colors in our shader

우리는 우리 모델의 정점을 칠해서 음영처리 된 부분을 표시하고 그 색을 우리의 음영처리 된 부분에 읽을 것이다.



This is how our model looks like when we map the vertx color

vertx 색상의 지도를 만들 때 우리의 모델은 이렇게 보인다.



To fix it we use a fancy rick: Vertex color

그것을 고치기 위해 우리는 화려한 릭을 사용한다


Then mult our lighting by the occlussion given by the vertex and done

그 다음 버텍스에 의해 주어지는 오쿨루젼으로 우리의 조명을 여러 번 혼합하여 완성한다.


To improve it even more, we'll add a third map: Combined Map

한층 더 향상시키려면, 3개의 맵: 결합 맵을 추가한다


We'll map if a pixel it's glossy or not in it's Red channel

우리는 픽셀에 광택이 나는지 아닌지를 빨간 채널에서 매핑할 것이다.


We'll use again the dot product and get the specular lights

우리는 다시 도트 제품을 사용할 것이고, 투시 조명을 받을 것이다.


We'll need the view direction(facing of the camera)

우리는 전망 방향이 필요할 것이다.


With the pow function we'll control the size of our reflection

파워 기능으로 우리는 반사되는 크기를 조절할 것이다.

We add a prop to control the Specular size

우리는 시편 크기를 조절하기 위해 받침대를 추가한다.



Then we apply the light color and the light attenuation to our specular component and mult the Glossy from the CombMap

그리고 나서 우리는 밝은 색과 빛 감쇠를 우리의 시야에 있는 성분과 콤비맵의 여러 가지 광택에 바른다.



Caution: darker it's bigger. The smaller the exponent, the bigger the highlight

주의: 더 어둡다. 지수가 작을수록 하이라이트는 커진다.


Finally, to not waste any memory, let's put at use the Green and Alpha channels of the CombMap

마지막으로, 어떤 기억도 낭비하지 않기 위해 콤비맵의 그린과 알파 채널을 사용하자.


We'll use Green channel to add extra shadows.(Caution: if you don't make this texture correctly shadows could pixelate)

우리는 그린 채널을 사용하여 그림자를 더 추가할 것이다.

(주의: 이 질감을 정확하게 만들지 않으면 그림자가 화소화될 수 있음)


Now, the Alpha channel will be used as an InnerLine mask. Alpha 0 it's a line

이제 알파 채널은 이너라인 마스크로 사용될 것이다. 알파 0 선이다.


COOL TRICK: if you structure a surface shader like this, you can mix regular vertex/fragment passes with surface passes

COOL TRICK: 만약 당신이 표면 하드어를 이렇게 구조화한다면, 당신은 일반 정점/약점 패스와 표면 패스를 혼합할 수 있다.


Very easy trick. We get the normal, and offset the vertex position along it a little bit.. Fragment just returns black

매우 쉬운 속임수 우리는 정상과 정점 위치를 약간 상쇄한다. 파편이 그냥 검은색으로 돌아온다.


PRO TIP : Renormalize the normal, some models have normals whith! =1 magnitide. This could result in inconsistent outlines.

PRO TIP: 정상화를 다시 하고, 어떤 모델들은 표준적인 기동을 가지고 있다! =1자석 이렇게 되면 윤곽이 일관되지 않을 수 있다.


To fix it just cull the front faces. We'll render only the back of the surface

그것을 고치기 위해서 앞면만 도려낸다. 우리는 표면만 다듬을 것이다.



All left to do it's add some props to control the outline color and thickness

남은 것은 윤곽의 색과 두께를 조절하기 위한 소품 몇 가지를 더하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
Shader "Custom/ToonShader"
{
    Properties
    {
        _LitOffset("Lit Offset",Range(0,1))=0.25
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _SSSTex("SSS Map",2D) = "black" {}
        _SSSColor("SSS Tint",Color) = (1,1,1,1)
        _CombMap("Comb Map",2D) = "white" {}
        _SpecPower("Specular Power",Range(0,100)) =20.0
        _SpecScale("Specular Scale",Range(0,10)) = 1.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200
 
        CGPROGRAM
        #pragma surface surf ToonLighting
 
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
 
        sampler2D _MainTex;
        sampler2D _SSSTex;
        sampler2D _CombMap;
        half4 _Color;
        half4 _SSSColor;
        half _LitOffset;
        half _SpecPower;
        half _SpecScale;
 
    
 
 
        struct CustomSurfaceOutput {
            half3 Albedo;
            half3 Normal;
            half3 Emission;
            half Alpha;
            half3 SSS;
            half vertexOc;//
            half Glossy;
            half Glossiness;
            half Shadow;
            half InnerLine;
        };
 
        half4 LightingToonLighting(CustomSurfaceOutput s, half3 lightDir, half viewDir ,half atten)
        {
            float oc = step(0.9, s.vertexOc);
            float NdotL = saturate(dot(s.Normal, lightDir))* atten;
            float toonL = step(_LitOffset, NdotL)*s.Shadow * oc;
            half3 albedoColor = lerp(s.Albedo * s.SSS, s.Albedo* _LightColor0 *toonL, toonL);
            half3 specularColor = saturate(pow(dot(reflect(-lightDir, s.Normal), viewDir),s.Glossiness* _SpecPower))
                                * toonL * _LightColor0 * s.Glossy * _SpecScale;
 
            //return half4(s.Glossy, s.Glossy, s.Glossy);
            return half4( (albedoColor + specularColor)*s.InnerLine,1);
 
        }
        
        struct Input
        {
            float2 uv_MainTex;
            float4 vertColor : COLOR;
        };
 
 
        void surf(Input IN, inout CustomSurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex)* _Color;
            half4 comb = tex2D(_CombMap, IN.uv_MainTex);// *_SSSColor;
            o.Albedo = c.rgb;
            o.SSS = tex2D(_SSSTex, IN.uv_MainTex) * _SSSColor;
            o.vertexOc = IN.vertColor.r; //
            o.Glossy = comb.r;
            o.Glossiness = comb.b;
            o.Shadow = comb.g;
            o.InnerLine = comb.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
 
cs




학습참고 https://www.youtube.com/watch?v=pMq--FUR5VE

 PostProcessing 

(Post Processing Stack은 2018년 이전 버전)


이번 학습목표 최종 결과




라이트를 사용하면 보통 라이트가 비추는곳은 빛이 나기때문에 괜찮은데

정작 빛을 내주는 라이트에서는 빛나지가 않다(후광이 없음)

라이트효과 참고 : https://funfunhanblog.tistory.com/94


이유는 라이팅 시스템이 그작업을 해주지 않기 때문이다.

그 작업을 해보려고 한다.


1) Post Processing 다운로드

유니티 Package Manager에서 다운로드 한다.

그런데 아마 유니티 2018년 이전 버전은 방법이 조금 다르다

Post Processing Stack 

에셋 스토어에서 다운로드 해준다.

다운로드 하면 post processing behavior스크립트를 추가 할 수 있다.

프로젝트창에서 Post - Processing Profile을 생성한다.

모든 셋팅값이 저장되는 곳이다.

생성한 Profile을 아까 추가한 스크립트 profile에 넣어준다. 

그런 뒤 값 세팅

참고 :  https://www.youtube.com/watch?v=o39SjRcA__U (14분 부터)


2) 포스트프로세싱 생성

a) 메인카메라에 Post Process Layer 컴포넌트를 추가한다. 

Layer는 volume이 작동할 레어를 설정해주어야한다. 없다면 추가한다.

Trigger는 This를 눌러준다.


b) Post-processing Profile을 생성한다.

모든 셋팅이 저장되는 곳 이다.


C) Post-processing Profile을 생성한다.

Is Global  : 전역으로 효과를 줄건지 체크

Profile : new를 눌러줘서 새로 만들어준다.

Add effect를 눌러서 추가할 셋팅요소들을 추가 한다. 이번에는 빛자체에서 후광 효과를 주기 위해서 Bloom만 추가해줬다. 

처음에 모든 셋팅설정 값들이 회색으로 나와서 안되는 줄 알았는데 저렇게 체크박스를 체크하니까 값을 넣을 수 있게 활성화가 됐다.


학습참고

1) https://github.com/Unity-Technologies/PostProcessing/wiki/Quick-start

2) https://github.com/Unity-Technologies/PostProcessing/issues/725

3) https://docs.unity3d.com/kr/2017.4/Manual/PostProcessing-Stack.html

4) https://www.youtube.com/watch?v=4Qjm4FQyZuE



서피스 셰이더



서피스 셰이더 동작원리


먼저 3D모델이 3D모델 지오메트리를 변경할 수 있는 어떤 함수로 넘겨진다. 

그런 다음, 몇몇 직관적인 속성을 사용하여 겉모습을 정의하는 어떤 함수로 넘겨진다.


마지막으로, 이 속성들은 지오메트리가 근처의 광원들에 의해 어떻게 영향을 받을지를 결정하는 라이팅 모델에 의해 사용된다. (각 모델의 픽셀들의 RGB색상으로 나타내짐)


서피스 함수


서피스 함수는 3D모델의 데이터를 인풋으로 가져와서, 결과값으로 렌더링 프로퍼티들을 반환한다. 

아래의 서피스 셰이더는 오브젝트를 흰색 diffuse를 갖는 오브젝트로 렌더링한다.



#pragma surface : 해당 셰이더를 위한 서피스 함수이름은 surf이고 라이팅 모델로는 램버시안 모델을 사용할 것이다 라는 뜻이다.


o.Albedo = 1; : 머티리얼의 알베도 즉, 재질 기본색상이 흰색이라는 것을 나타낸다.


최초의 3D모델로부터의 어떠한 데이터도 사용하지 않지만,

그럼에도 인풋 구조체를 정의하는 형식이 필요하다.




서피스 아웃풋


SurfaceOutput구조체 안에는 머티리얼의 최종 모습을 결정하기 위해 사용되는 여러가지 프로퍼티들을 포함하고 있다.


fixed3 Albedo : 기본색상

fixed3 Normal : 반사각을 결정하는 면의 방향

fixed3 Emission : 이 오브젝트가 스스로 생성하는 빛의 양

half Specular : 머티리얼이 빛을 반사하는 정도(0~1)

fixed Gloss : 스펙큘러 반사가 퍼지는 정도

fixed Alpha : 머티리얼의 투명한 정도


Cg/HLSL은 전통적인 float타입을 지원한다. 하지만 보통 32비트의 정밀도는 거의 필요하지 않을 것이고,

보통 16비트 정도면 충분하기 떄문에 half타입을 주로 사용하게 될 것이다.



텍스처 샘플링


텍스처를 붙이는것을 배우기전에 텍스처 매핑이 어떻게 일어나는지 알아보자

텍스처를 입힐 모델은 여러 개의 삼각형들로 만들어져있고, 각각의 삼각형은 3개의 버텍스로 구성된다.

데이터(UV와 색상)는 이러한 각 버텍스들 안에 저장된다.


삼각형의 버텍스들을 통해 텍스처 상에 맵핑되는 0에서 1사이의 정규값을 가져 올 수 있는데 이것이 텍스처 UV좌표이다.

즉, 3D오브젝트에 텍스처를 매핑하려면, 각 버텍스의 UV좌표가 필요하다.




_MainTex라는 이름으로 텍스처 프로퍼티를 선언했는데 이 프로퍼티는 머티리얼

인스펙터를 통해 접근가능하도록 처리가 되도록 한 것이다.

<쉐이더를 적용한 머티리얼 인스펙터창>


현재 픽셀의 UV데이터는 float2 uv_MainTex에서 추출된다.


tex2D는 텍스처와 UV값을 넘기면, 이에 해당하는 RGB색상으로 반환한다.

tex2D함수는 텍스처를 임포팅할 때, Unity에 의해 직접 설정될 수 있는 다른 파라미터들을

계산에 고려하게 된다.



서피스 인풋

서피스 인풋인 Input의 필드들을 Unity가 계산 할 값들로 채울 수 있다.

예를 들어, float3 worldPos를 추가함으로써 surf에서의 해당 점에 대한 월드 포지션을 가지고 초기화 되는데

이것은 특정 점으로부터의 거리에 의존적인 이펙트를 생성하는데 사용된다.


그 밖의 다른 인풋들

- float3 viewDor : 카메라의 방향(뷰 방향)

- float4 name : COLOR : name변수에 버텍스 색상을 포함한다.

- float4 screenPos : 스크린 좌표 상의 해당 픽셀의 위치

- float3 worldPos : 해당 픽셀에 대한 월드 좌표 상의 위치



버텍스 함수


surf함수로 보내기 전에 이들을 수정/변경해준다. 

surf함수가 RGBA공간 안에서 색상을 다루는 반면, 공간에의 3D점을 제어하기 위해서는 버텍스 모디파이어 사용방법을 알아야한다. 


3d모델을 원래의 모습보다 좀 더 통통하게 만드는 셰이더를 예제로 만들어보자

이렇게 만드려면 모델의 삼각형을 삼각형면이 향하는 방향을 따라서 확장시키면 된다. 

삼각형면이 향하는 방향은 결국 노멀값으로 나타내며, 삼각형 자신의 포면에 수직인 단위 벡터이다.

그렇다면, 이제 다음과 같이 노멀 방향으로 버텍스를 확장하려면 다음과 같은 수식을 사용할 수 있다.



#pragma surface surf Lambert vertex:vert : vert라는 이름을 갖는 버텍스 모디파이어를 설정한다.


실제 vert함수 코드를 보면 ,(v.vertex.xyz += v.normal * _Amount; )

버텍스 위치를 받아서 노멀을 따라 해당 버텍스를 밀어낸다.


appdata_full 구조체는 해당 현재 버텍스의 모든 데이터를 포함하고 있다. 


<결과 화면>



눈 쉐이더


surf함수와 vert함수 이 두가지를 사용하는 snow이펙트가 있다.

어떤 모델의 삼각형 위에 눈이 쌓이는 것을 시뮬레이션하는 것이다.

처음에는 _SnowDirection을 직접 마주하는 삼각형들에만 적용되다가, _Snow가 증가함에 따라서

하늘을 향하지 않는 삼각형들도 결국엔 영향을 받게 되는 방식이다.


우선, 삼각형이 하늘을 향해 있다는 의미를 파악해보자

_SnowDirection은 눈이 내리는 방향을 의미하며 단위벡터의 형태이다.

그것들이 어떻게 정렬되어 있는지를 확인하는 방법중 가장 쉬운 방법은 눈 방향으로 법선(노멀)을 투사하는 것인데,

두 벡터 모두 크기는 1이므로, 결과값은 +1(같은 방향)에 -1(반대 방향)사이가 될 것이다.

이것은 내적과 같다는 것을 Cos세타와 동일하다는 것만 알면된다.

특정 _Snow값보다 도트 연산이 커지게 된다는 것은


  • cos (\ theta) \ geq +1 두 방향이 같은 경우에만 참입니다.
  • cos (\ theta) \ geq 0\ theta90도 미만일 때 true입니다 .
  • cos (\ theta) \ geq -1 항상 사실입니다.

<결과 화면>


벡터 사이의 코사인 값을 수동으로 계산하는 대신,

Cg는 dot라는 이름의 도트 연산 구현을 포함하고 있다.

float4 sn = mul(_SnowDirection, _World2Object);

if (dot(v.normal, sn.xyz) >= _Snow)

v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;

이 부분은 노멀을 월드좌표로 바꾸기 위해서 조금 다른 메소드를 활용하고 있다.

WorldNormalVector함수는 버텍스 모디파이어 안에서는 사실상 이용할 수 없기 때문이다.



http://www.alanzucconi.com/2015/06/17/surface-shaders-in-unity3d/

https://jinhomang.tistory.com/137

라이트맵


라이트맵을 학습하기 전에 알아둬야 할 것이 있다.


GI

우리 모두는 빛이 들어옴으로써 눈이 작동하는 것은 알게된다. 

직접 광원을 바라보고 있지 않는 한 GI(전역광)은 우리의 눈으로 들어오기 전에 물체 사이를 돌아다닌다.

빛은 비추는 물체마다 다르게 반사되거나 흡수되어 새로운 방향으로 보내게된다.


컴퓨터는 그래픽에서 빛이 반동하는걸 두 가지 카테고리로 나눈다.

직접광, 간접광

직접광

광원으로부터 빛이 한번 튕겨지고 눈에 들어오는것을 말한다.

간접광

빛이 눈에 들어오기 전에 여러 표면에서 튕겨져서 우리 눈에 들어오는 광이다.


ENLIGHTEN = realtime 

리얼타임 GI를 전달하는 움직이는 라이트나 오브젝트가 있을 때 쓰면 좋지만 문제는 연산비용이 들기 때문에 미리 라이트를 계산해서 오브젝트에 덮어씌우는 큰 텍스처를 만들어 주는것이 좋다. 

=> 그것이 바로 베이킹


프로그레시브 라이트맵퍼

인라이튼을 써서 라이트를 굽는것말고 프로그레시브 라이트 맵퍼를 사용하는것이 좋다. 

프로그레시브 라이트 맵퍼는 경로 추적 기반의 라이트맵퍼로 주변에서 튕겨지는 빛을 사실적으로 시물레이션 할수 있다. 


이 방법은 카메라에서 광원한테 점진적으로 광선을 보내는것인데,  그래서 렌더링 할 때 에디터에서 바로바로 결과를 볼 수 있고, 씬을 다시 렌더링 할 필요없이 리이팅 프로퍼티를 설정 할 수 있다. 


테스트하기 위해 벽을 만들어 방처럼 오브젝트를 위치시켰다.

그리고 Directional Light은 삭제했다. (그래서 어둡게 보임)


Light가 될 plane 생성

왼쪽 벽에 붙이고 material을 생성 한 후 Emission을 Color 오렌지 색을 넣고 수치값을 1.5로 설정했다.

생성한 material을 plane에 넣어준다.


아직 아무 반응이없다. Emission이있는 오브젝트는 리얼타임에서는 빛이 퍼지지 않기 때문이다.

빛을 주려면 베이크를 해야한다. 

이 효과를 보기 위해서는 움직이지 않는 오브젝트들이니까 베이크해도된다고 유니티에게 알려야한다.

방, 큐브, plane모두 Static 객체로 만들어준다. 

Static 오브젝트를 만든순간 유니티는 베이크를 시작한다.

오렌지색 라이트가 생겼다.

반대편에는 블루라이트를 추가해줬다. 이미션값은 2로 설정

둘다 켜주면 뭔가 빛세기가 약해지는데 이 이유는 잘모르겠다 ㅠ


그 다음에 Lighting Scene을 설정해준다.

Scene 탭의 설정은 개별 게임 오브젝트가 아니라 전체 씬에 적용/ 이 설정은 조명 효과와 최적화 옵션도 제어

실내를 연출할 것이기 때문에 Ambient Color는 있어서 안된다. 

빛은 방금전에 만든 라이팅만 있어야하기 때문에 검은색으로 맞춰준다. (초기 셋팅은 회색으로 되어있음)


Realtime Lighting은 체크 해제 해준다.


Lightingmapping Seting설정

Lightmapper -> Progressive 로 바꿔준다.


그런데도 변화가 업다. 이유는?


프로그레시브 라이트 맵퍼를 사용할 때에는 이미시브 메테리얼로 가서

Global IlluminationRealtime 에서 Baked로 바꿔야한다. 


베이크 중

노이즈가 보이는데 베이크중이라서 그렇다.


베이크 후

라이팅 매퍼의 몇 가지 설정들에 대해서 알아보자

Lightmap Size : 오브젝트사이즈가 작다면 줄여주자 같은 결과이지만 라이트맵 용량이 크게 부여 되어 있을 수 있다.

Compress Lightmap : 라이트맵 압축 여부 

=> 용량이 클 수록(높은 사양)일 수록 베이크시간이 오래걸리기 때문에 작업수정할때는 낮췄다가 최종적으로 올리도록 한다.

Ambient Occlusion : 근처 두 오브젝트간의 그림자를 발생시킨다.







학습참고

1) https://www.youtube.com/watch?v=o39SjRcA__U

1) https://docs.unity3d.com/kr/2018.2/Manual/class-LightmapParameters.html

2) https://chulin28ho.tistory.com/443

3) https://docs.unity3d.com/kr/current/Manual/GlobalIllumination.html



라이트맵


라이트맵이란?

유니티 프로젝트를 새로 생성하면 "Directional Light"이 하이어라키상에 만들어져있다. 연극에서 무대를 비추는 조명이 있다면, 이 것은 게임장면을 비추는 조명이다.

당연히 게임이 진행되는 동안 조명 연산비용이 들어간다. 이  조명연산 또한 가볍지 않기 때문에 라이트맵을 사용한다.


라이트맵은 어떻게 연산비용을 줄이는가?

조명을 텍스처로 만들어서, 실시간으로 조명 연산을 하지 않아도 조명이 비추는것 처럼 표현 하는것이다.  


정적 오브젝트 (움직이지 않는 오브젝트)

조명을 텍스처로 만든다라고 하면, 물체에 있는 오브젝트에 그림자 등 조명이 비춰져 있는 상태를 미리 그려 놓는다는 것인데, 당연히 움직이는 오브젝트는 해당이 안되는것이다.(움직이는 오브젝트의 그림자를 미리 그려 놓을 수 없기 때문, 라이트맵의 종류에따라 표현방법이 다름)



라이트 맵 베이크 방법

라이트맵을 만드려면 일단 조명이 움직이지 않는 환경,물체 오브젝트를 베이크를 해야한다. 


1) 라이트 설정

라이트에는 필수로 두가지 설정이 필요하다.

첫 번째, Static 정적오브젝트로 만들어준다.

두 번째, Mode -> Baked or Mixed (Baked로 라이트맵을 만드는것과 Mixed로 만드는것은 차이점이 있다.)


2) 라이팅 셋팅 창

Window -> Rendering -> Lighting Settings창을 활성화 시킨다.

Scene탭에서 

라이트맵 종류에 대해서는 다음번에 정리를 해야겠다.

그 다음 Generate Light를 Bake해준다.

(Auto를 켜주면 변할때마다 자동으로 원치 않을때 Bake를 해서 불편 한 것같다.)


Baked Lightmap을 보면 텍스쳐럼 구워진게 보인다.



라이트맵# 2

https://funfunhanblog.tistory.com/94


전역 조명(GI)은 직접 표면에 닿는 광원(직접광)뿐만 아니라 다른 표면에 부딪혀 반사되는 광원(간접광)을 모델링하는 시스템입니다. 오브젝트는 서로의 형상에 영향을 미치기 때문에 간접 조명을 모델링하면 가상 세계를 더 현실적이고 연결된 것처럼 만들 수 있습니다. 예를 들어 빨간색 소파에 햇살이 비치면 붉은 광원이 그 뒤에 있는 벽 위로 반사됩니다. 또한 동굴 입구의 바닥에 햇살이 비춰지면 동굴 내부로 반사되어 동굴 안쪽이 밝게 비춰질 수 있습니다.

출처 : https://docs.unity3d.com/kr/2018.2/Manual/GIIntro.html


학습 참고 : 

1) https://chulin28ho.tistory.com/441?category=458928

2) https://docs.unity3d.com/kr/2018.2/Manual/class-LightmapParameters.html


미니맵 만들기 ( RenderTexture)


미니맵을 만드는 방법도 여러가지가 있겠지만 

이번에 학습할 내용은 랜더텍스처로 카메라를 추가해 캐릭터를 밑에 바라보게 하는것이다.

(보통은 카메라도 연산비용이 크기 때문에 카메라를 추가하지 않고 

캐릭터 실제 움직이는 위치를 계산해 이미지로 작은 미니맵을 만들어 준다고 한다.)


1) 카메라 추가 

위에서 밑을 바라보는 카메라를 만들어 미니맵처럼 보이기위함이다.

하이라키창에서 마우스 오른쪽 클릭하고 카메라를 추가해준다.

이름은 기존 카메라와 헷갈리지 않게 미니맵카메라 정도로 바꿔준다.


2) 캐릭터 플레이어를 대신 할 오브젝트 추가

새로 추가한 카메라가 플레이어 캐릭터를 비추면되지만 보통 캐릭터에는 폴리곤과 많은 이미지들이 붙어 있기 때문에 미니맵에는 이렇게 보일 필요가 없다. 

캐릭터의 어디에 있는지 위치정도만 보여주면 되기 때문에 나는 큐브를 생성하고 플레이어 자식으로 넣었다.

(미니맵 카메라는 사각이미지로 보일것이다.)

그리고 이 큐브 레이어를 'playercube'로 해준다. 이유는 밑에 설명


3) RenderTexture추가

랜더텍스처를 추가한다.

Render Texture는 런타임에서 생성 및 갱신되는 특수한 Texture로 카메라가 비추는 화면을 텍스처럼 가지고 있고 원하는 이미지에 그려줄 수 있는 텍스처라고 생각하면 된다.


4) 미니맵카메라 인스펙터 설정

새로 추가한 카메라에 셋팅을 해줘야한다. 

첫 번째로 카메라가 Culling Mask를 설정해준다. 

Culling Mask는 카메라가 보여질 레이어들은 체크하는 것이다. 맨 처음에는 Everything으로 되어 있을것이다. 

방금 추가한 Cube는 'playercube'이기 때문에 이것은 체크해주고 player(실제 캐릭터)는 체크 해제 해준다.

나머지는 그릴 필요가 있는지 없는지 체크해서 상황에 맞게 설정해준다. 


두 번째는 Target Texture를 아까 생성한 RenderTexture를 추가한다.


5) Raw Image 미니맵 이미지 생성

캔버스 밑에 Raw Image를 생성한다. 

일반 Image와 다르게 텍스처를 설정 할 수 있기때문이다.

Texture에 여기에도 아까 생성한 랜더텍스처를 넣어준다.


플레이 (우측 상단)


여기서 추가 셋팅은 기존 카메라에서는 당연히 하늘을 보게 되면 아까 추가한 큐브가 보일 것이다. 

기존 카메라에서 Culling Mask 'playercube'를 체크 해제해서 안보이게 해주어야한다.


랜더 텍스처 : https://docs.unity3d.com/kr/530/Manual/class-RenderTexture.html

유니티) 셰이더 #1




3D모델은 버텍스라고 불리우는 3D 좌표들의 모음이다.

그것들은 삼각형을 이루기 위해서 서로간에 연결이 되어 있다.

 각 버텍스에는 몇가지 다른 정보들을 포함할 수 있다.

색상값, 방향(노말) 그리고 UV값이라고 불리는 텍스처 매핑을 위한 좌표들이 있다.





모델은 머터리얼없이는 렌더링이 불가능하고, 머터리얼은 셰이더와 그 셰이더 속성값을 포함한 일종의 덮개?이다.



셰이더구조


유니티는 서피스 셰이더와 버텍스버텍스 와 프래그먼트 셰이더 두 가지 타입의 셰이더를 지원한다. + 픽스드 함수 셰이더도 있다.


먼저 셰이더 기본 구조를 알아보자 어떤 셰이더든 구조는 모두 아래와 동일하다.



SubShader섹션은 여러개가 될 수 있다. 여기에 실질적인 GPU에 대한 명령을 담겨 있는데, 유니티 엔진은 그래픽 카드에 호환되는 것을 찾을 때까지 이것들을 순서대로 실행을 시도한다. 

여러 플랫폼을 고려하여 여러개 섹션을 나누는것이다.



프로퍼티

셰이더 프로퍼티는 머터리얼의 인스펙터 안에서 볼 수 있다. 따라서 인스펙터를 통해 그 값을 변경할 수도 있는 것이다. (C#의 public 필드 느낌)


스크립트를 사용하는 것과 다르게 머터리얼은 Asset이라서 프로퍼티 변경값이 그대로 남아있는다. 따라서 멈춘 후에도, 머티리얼에서 변경헀던 부분의 남아있는 것을 확인할 수 있다.



 첫번재와 두번째 프로퍼티에 사용된 2D 타입은 파라미터가 텍스처라는 것을 뜻한다. 

이들은 white, black, gray로 초기화 될 수 있다. 

또한 텍스처가 노멀맵으로 사용될 것을 의미하는 bump를 사용 할 수도있다.


Vector와 Color는 각각 X,Y,Z,W와 R,G,B,A이렇게 항상 4가지 요소를 가진다.


프로퍼티는 유니티에 의해 셰이더 내의 숨겨진 변수에 액세스 할 수 있도록 할 뿐이지 실제로 적용되지는 않는 것이다

그렇기 때문에 SubShader 본문을 정의 해야한다.



렌더링오더

 SubShader섹션은 셰이더의 실제 코드를 담고 있다. 이 코드는 C와 비슷한 Cg / HLSL로 작성된다.

대략적으로, 셰이더의 바디느느 이미지의 각각의 픽셀을 위해 실행된다. 


셰이더 안에서 수행할 수 있는 명령의 수가 제한 되지만, 계산을 여러 패스(pass)로 나누어 이런 것을 피할 수 도 있다. 



Tag는 우리가 작성 중인 셰이더의 특정 프로퍼티에 대해 Unity에게 얘기하는 수단이다.


여기서 말하는 특정 프로퍼티를 예를 들자면, 렌더링이 되어야 하는 순서를 뜻하는 Queue

모델을 어떻게 렌더링이 되어야 하는지를 뜻하는 RenderType같은 것들이 있다.


쉽게 Queue는 머터리얼의 렌더링 순서를 제어하기 위해 Queue태그를 지정할 수 있는 것이다.

(숫자가 작을 수록 먼저 그림)


Background(1000) : 배경과 스카이박스르 위해 사용

Geometry(2000) : 대부분의 불투명 오브젝트를 위해 사용되는 기본 레이블

Transparent(3000) : 투명 프로퍼티를 가지는 머티리얼(유리, 불, 파티클, 물 등등)에서 사용된다.

Overlay(4000) : 렌즈 플레어같은 이펙트, GUI, 텍스트와 같은 것을 그릴때 사용



서피스 셰이더


머티얼이 매 순간 빛에 의해 영향 받으며 현실감 넘치게 시뮬레이션하고 싶은 경우

라이트(빛)가 반영되는 방법에 대한 계산은 내부에 숨기고, surf라는 함수 안에서 알베도, 노멀, 반사율등 과 같은 "직관적인" 속성만 정해주는것이다.



예제코드를 보면 sampler2D _MainTex라인을 사용하여 입력되는 것을 알 수 있다. 

이것은 surf함수에서 머티리얼의 Albedo속성으로 설정된다.



버텍스/프래그먼트 셰이더


버텍스/프래그먼트 셰이더는 거의 GPU가 삼각형을 렌더링하는 방법대로 동작하게된다.

그리고 라이트와 어떻게 작용하는지에 대해 특별히 내장되거나 정해진 컨셉이없다. 

모델의 지오메트리는 먼저 버텍스를 변경할 수 있는 vert라는 함수로 전달된다. 

그 후에, 각 픽셀에 대해서 최종 RGB색상을 결정하는 frag라는 함수로 전달된다.

보통 이 유형의 셰이더는 2D이펙트, 후처리 또는 서피스 셰이더로 표현되기 어려운 복잡한 3D특스 이펙트를 구현하는 유용하다. 


<적용>

vert함수 안을 보면, 버텍스들을 그 원래의 3D공간에서 스크린 상의 최종 2D포지션으로 변환하는 코드가 있다. 

유니티는 이 변환에 대한 수학적인 내용을 감추기 위해서 UNITY_MATRIX_MVP를 사용하여 단순화 하였다. 이 후, frag함수를 반환하면 모든 픽셀이 빨간색으로 칠해진다.

https://unity3d.com/kr/learn/tutorials/topics/graphics/gentle-introduction-shaders


코루틴Coroutine 작동원리



코루틴?



코루틴은 실행을 중지하여 Unity에 제어권을 돌려주고, 그러나 계속할 때 는 다음 프레임에서 중지한 곳부터 실행을 계속할 수 있는 기능. 함수를 호출하면 반환값을 반환하기 전에 실행 완료된다. 즉 , 함수에서 수행되는 작업은 하나의 프레임에서 수행된 다는 것을 의미하고, 함수 호출은 절차적 애니메이션을 포함하거나 시간의 경과 함께 일련의 이벤트에는 사용할 수 없다.

<유니티 공식 메뉴얼>


유니티로 게임 제작을 하다보면 코루틴을 많이 사용하게 된다. 쓰레드의 대해서 정확하게 알지는 못하지만 유니티는 단일 쓰레드로 동작한 다는것은 알고 있었다. 그렇다면 유니티에서 멀티쓰레드처럼 멀티태스킹이 불가능한걸까?  멀티 쓰레드처럼 비슷하게 작동하게 해주는 것이 코루틴이다. 그렇다고 멀티쓰레드가 되는것은 아님


코루틴은 어떻게 작동될까?

코루틴은 IEnumerator를 반환한다. C#의 이터레이터 인터페이스는 프로그래머가 IEnumeraotr를 구현 할 수 있도록 도와준다. 

진입하는 지점을 한 개인 보통 서브루틴 방식과 다르게  진입하는 지점까지 여러 개를 가질 수가 있다. yield return을 통해 그 바로전 시점을 기억하고 다음 호출하게 될떄 그 다음부터 실행이 되는 것이다. 

유니티에서는 대부분 그 시점을 Update에서 체크한다. 


코루틴을 사용할 때 IEnumerator와 같이 사용한다. 이 IEnumerator는 데이터의 목록을 하나씩 넘겨줄 때 사용되는 인터페이스인데, 코루틴은 호출 한 함수와 상호작용하면서 진행되는데 자신을 호출한 함수에 데이터를 하나 넘겨주고 쉬게되는데, 받은 함수에서는 데이터를 받고 처리한 후에 코루틴에게 다음 데이터를 달라고 요청하게 된다. 





(서브 루틴 : 일반적인 함수에서 사용되는 작동개념인데 함수는 시작할 때 진입하는 지점이 저장하고, return구문에 의해서 종료되는 지점을 저장한다. 

Iterator :컬렉션에 대해 사용자 지정 반복을 수행, yield return 문을 사용하고 각 요소를 한 번에 하나씩 반환한다. 이터레이터는 현재 위치를 기억하고 다음 반복에서는 다음 요소를 반환.)






학습 참고:

1). https://m.blog.naver.com/PostView.nhnblogId=captainj&logNo=221102892952&proxyReferer=https%3A%2F%2Fwww.google.com%2F

2). https://seungngil.tistory.com/

3) .http://blog.naver.com/PostView.nhn?blogId=yoohee2018&logNo=220699457038&parentCategoryNo=&categoryNo=&viewDate=&isShowPopularPosts=false&from=postView


Physics.Raycast 오브젝트 검출하기




레이를 쏴서 그 오브젝트가 어떤 오브젝트인지 파악하고 각각 다른 액션이 필요하다.


타입을 나누는 이유는 캐릭터가 검출대상(box,무기,아이템등..)의 대한 메소드를 캐릭터 스크립트에서 모두 정의 하는것은 다향성부분에도 떨어지고, 코드도 보기가 좋지 않다. 오브젝트 객체가 각각 가지고 있는 스크립트에서 정의해주고 캐릭터는 어떤 상황에 어떤 함수를 실행할 것 인지 제어해 주는것이 좋을 것 같다.

그래서 타입을 나누고, 인터페이스를 활용한다.  


타입 나누기

타입의 따른 설정을 주기 위해 열거형으로 만들어줬다.


인터페이스 만들기 

InterType : 어떤 타입인지 받아 오기위함

enable : 오브젝트 스크립트에서 제어 하기 위함

ActionInter : 입력버튼을 실행 할 경우 실행되는 메서드

ShowInter, NonShowInter : 각각 상황에 맞게 실행 


박스 스크립트 부분(인터페이스)

Box, Npc, Monster 등등 IInteraction을 인터페이스 구현이 필요하다.


플레이어스크립트 레이캐스트

if (hit.transform.CompareTag(Tag.RAYCAST_TAG_INTERACTION)) 

=> 오브젝트가 INTERACTION 태그인지 확인한다.

if (iInteraction.enable==true)

=> 오브젝트 enable를 체크한다.

UI_Hud.Ins.StringSetText(iInteraction.InterString);

=> 오브젝트 스크립트에 있는 InterString를 UI_Hud에 보낸다.(싱글턴)

UI_Hud.Ins.StringSetText

=> 각각 오브젝트 스크립트에서 설정한 텍스트 박스를 띄워준다.

 


ScriptableObject


스크립팅 가능한 오브젝트

메소드를 실행하는 스크립트가아닌 데이터를 사용하기 위한 스크립트이다.




생성방법

[CreateAssetMenu(fileName = "New HeroData", menuName = "Sword HeroData", order = 2)]

public class test : ScriptableObject

{


}

유니티에서 스크립트를 생성하면 Monobehaviour를 자동으로 상속 받게 되는데 지우고

ScriptableObject를 상속받아온다.


[CreateAssetMenu] : 없어도 되지만 있으면 편함

fileName : 새로 제작하게되면 임시로 생성된다. 

menuName : 유니티 메뉴에 표기되는 이름

order : 메뉴에서 보일 순서

Assets -> Create 보면 방금 생성한 HeroData가 보이게 된다.


SerializeField의 원본 데이터들은 수정되면 안되기 때문에 

데이터 값을 받을 게터를 public으로 추가해준다.

public class test : ScriptableObject

{

    [SerializeField]

    string heroName;

    [SerializeField]

    string description;

    [SerializeField]

    int goldCost;


    public string sName => heroName;

    public string sDesc => description;

    public int    nCoin => goldCost;

}


사용법

데이터 값 입력

ScriptableObject를 받을 코드 작성

public class Scene : MonoBehaviour

{

    public Text m_txtUI;

    public test m_Data;

    void Start()

    {

        m_txtUI.text = string.Format(m_Data.sName + " // " + m_Data.sDesc);

    }

}

Data 매칭 인스펙터 창에서 끌어다 놓는다

실행


데이터를 한 오브젝트나 클래스에서 사용하기에는 괜찮은 것 같다. 데이터가 왔다갔다 하거나 값이 변하는 데이터는 쓰기에 어려울 것 같다.


학습 참고 : https://docs.unity3d.com/kr/current/Manual/class-ScriptableObject.html

https://m.blog.naver.com/yoohee2018/220725032540

스태틱 오브젝트


게임이 실행되는 동안 움직이지 않는 오브젝트를 스태틱으로 설정해 놓으면 유니티 에디터 상에서 미리 계산해놓아 실시간으로 계산하지 않도록 하여 비용을 줄이는 최적화 방법이다.



  • Lightmapping: 씬을 위한 고급 라이팅;
  • Occluder and Occludee: 특정 카메라 위치로부터 오브젝트의 가시성에 기반한 렌더링 최적화;
  • Batching: 여러 오브젝트를 더 큰 하나의 오브젝트로 결합하는 렌더링 최적화;
  • Navigation: 씬 안에서 캐릭터가 장애물을 지나게끔 만들어주는 시스템.
  • Off-mesh Links: Navigation 시스템에 의해 만들어지는 씬에서의 불연속적인 영역 간의 연결.
  • Reflection Probe: 모든 방향에 대한 주위 환경을 둥근 뷰의 형태로 캡처합니다.

유니티는 베이크된 GI(전역 조명)을 간접광에 대한 내용을 미리 계산해서 데이터화를 미리 가능하다. 




Occlusion Culling오클루젼 컬링


땅을 스태틱오브젝트로 설정하고 카메라가 비추는 곳만 그리게 된다.

오클루젼 컬링 (Occlusion Culling)은 오브젝트가 다른 오브젝트에 의해 가려져서 카메라에 보이지 않을 때 해당 오브젝트의 랜더링을 비활성화 하는 기능이다.

+ 이거는 절두체컬링이네.. 

정적오브젝트는 리소스가 사전준비가 되어있어야 하며 추가/변경 될때는 베이크를 해주어야 한다.  



참고자료 : https://docs.unity3d.com/kr/530/Manual/StaticObjects.html

2D Physics


유니티에는 3D와 마찬가지로 2D에도 물리 시뮬레이션 엔진이있다. 

Rigidbody2DCollider 2D가 대표적인 컴포넌트이다.


일단 가장 기본적으로 2D 물리에서는 position Z값은 의미가 없다.


간단히 테스트를 해보면


박스는 position Z값 15 

막대는 position Z값 0


에디터 3D모드로 보면 막대와 박스가 떨어져 있지만 박스는 막대에 물리가 적용되어 지나가지 않는다.

2D는 Z값을 변경이 가능하지만 2D Physic는 물리상호작용은 Z가 0에서 일어난다 





Hinge Joint 2D

2D에도 Hinge Joint가있다.



Anchor

어떻게 회전할 것 인지를 선택할 수 있다.


Connected Anchor

씬 어디서 배치될것 인가를 설정하는 곳이다.
이 값은 연결된 본체의 Local Space에있다.






https://www.youtube.com/watch?time_continue=5&v=l6awvCT29yU

참고 : https://www.youtube.com/watch?v=xEVXE7_YILk&feature=youtu.be

Physics 2D Manager






물리가 자세하게 필요한 게임이라면 여기서 설정을 통해 원하는 방식의 게임으로 제작이 가능할 것같다.

유니티 도큐에서 보면 질량의무게, 속도 임계값, 선형보간등을 설정이 가능하다.


이번에 테스트 한 것은 맨 밑에 많은 체크박스로 이루어진

Layer Collision Matrix이다. 


Layer Collision Matrix 레이어에 따른 충돌여부를 설정할 수 있다.

간단하게 테스트로 해보았다. 



1번 막대에는 레이어를 Default로 설정하고

2번 막대에는 ground로 설정했다. 

그리고 1,2번막대에는 모두 충돌체크를 할 수있도록 Collider컴포넌트를 추가했다.

그런다음 player와 Default 레이어에 해당하는 체크박스는 해제한다.


이제 player를 떨어트려보면.


예상으로는 막대에 콜라이더가 있기 때문에 1번막대에서 서있을것이다.




하지만 1번 막대를 통과하고 2번막대는 통과하지 못한다.


Layer Collision Matrix에서 설정한대로 player는 ground만 충돌 체크를 하게된다. 


활용도는 점프가 가능한 지형(통과 가능한)과 점프가 불가능한(통과하지 못하는)지형으로


구분하여 게임의 재미요소를 추가가 가능할 것같다.




참고 : https://docs.unity3d.com/kr/530/Manual/class-Physics2DManager.html

오브젝트 풀링 Object Pooling

스페이스바를 누르면 총알을 생성되고, forward방향으로 이동하며 3초후에 사라진다.



1) 처음에 10개를 만들어 놓는다.

2) 플레이어가 스페이스바를 누르면 하나씩 SetActive(true)로 활성화 시켜 마치 새로 생성된 것처럼 보여준다.

3) 정해둔 시간이 되면 총알은 SetActiv(false)되어 삭제된 것처럼 보여준다.

4) 만약 씬안에 10개가 이상 총알이 생성돼야 한다면 추가적으로 10개씩 생성해준다.


위 gif처럼 천천히 총알을 쏘면 10개로 활/비활성화로 돌려 쓸수 있다.

하지만 빨리 쏘게되면 추가적으로 10개가 되는것을 볼 수 있다.


오브젝트를 생성과 삭제를 하지 않고 활/비활성화로 구현한다.

( Instantiate과 Destroy을 쓰지 않는 기법이다.)


오브젝트를 복제 생성하고 삭제하는 Instantiate과 Destroy을 사용하게 되면 가비지 콜렉터를 호출하게 된다.

가비지 콜렉터란?

C#에서는 메모리 관리를 프로그래머가 따로 관리 하지 않는다.

가비지 컬렉터는 메모리에 있는 참조가 끝난 객체를 쓰레기 치우는 것처럼 소멸 시키는 역할을 한다.

=> 가비지 컬렉터 호출이 많이 발생 할 수록 성능이 낮아 진다.



pool 컬렉터 셋팅

LinkedList<Bullet> m_lipool = new LinkedList<Bullet>(); //풀에 담을

LinkedList<Bullet> m_liActive = new LinkedList<Bullet>(); //(생성)활성화된 총알을 담을


    const int nMAKE_BULLET_COUNT = 10;


    public void Init(Transform a_objRoot, GameObject a_objBulletOrigin) 

    {

        m_objRoot      = a_objRoot;

        m_BulletOrigin = a_objBulletOrigin;


        MakeBullet(nMAKE_BULLET_COUNT);

    }


~~생략

풀에 담을 오브젝트를 담을 컬렉터와 게임에서 보여질 오브젝트를 담을 컬렉터를 만들어준다.



pool에 오브젝트(값) 삽입
private void MakeBullet(int a_nCount) { for (int i=0; i<=a_nCount; ++i) { var b = Instantiate(m_BulletOrigin).GetComponent<Bullet>(); b.transform.parent = m_objRoot.transform; b.gameObject.SetActive(false); //비활성화 m_lipool.AddLast(b); } }

씬에 생성을 하고 비활성화로 해주고 LinkedList풀에 Add해준다.


오브젝트 생성(생성될 위치, 데이터)

public void AddShot(Vector3 a_vcPos, ref BulletData a_vcData)

    {

        if(m_lipool.Count == 0) 

        {

            MakeBullet(nMAKE_BULLET_COUNT); //10개 

}

            var bullet = m_lipool.First.Value; 

            m_lipool.RemoveFirst() //삭제

            bullet.Init(ref a_vcData); 

            bullet.transform.localPosition = a_vcPos;

            bullet.gameObject.SetActive(true);

            m_liActive.AddLast(bullet); 

    }

오브젝트풀 카운트가 0이되면 추가적으로 다시 10개를 생성한다.

첫번째로 담았던 오브젝트를 위치와 데이터를 정보를 입력해주고 활성화 해준다.

m_lipool 풀에서는 지워준다.

m_liActive 에 추가 해준다.


pool반환

    List<Bullet> m_listemp = new List<Bullet>(); //리스트 생성

    private void Update()

    {

        foreach(var b in m_liActive) //활성화된 풀중에서

        {

            b.DoUpdate(); //DoUpdate(총알 위치 업데이트)

            if(b.IsDie == true) // 사라질 시간이 됐다면

            {

                m_listemp.Add(b); //사라진 총알은 리스트에 추가

            }

        }

        foreach(var b in m_listemp) //리스트에 저장된 사라진 총알들

        {

            b.gameObject.SetActive(false); //비활성화

            m_liActive.Remove(b); //활성화된 풀삭제

            m_lipool.AddLast(b); //다시 풀에  넣는다 (뒤에)

        }

        m_listemp.Clear(); //넣어준 리스트 클리어

    }

풀에 넣을 리스트를 생성해준다 중간 바구니라고 생각하면 쉽다. 

1) 사라질 시간이 될 총알들은 비활성화 해주고  

2) m_liActive에 지운다.

3) 그 총알들을 리스트에 담는다.

4) 바구니에 담은 총알들을 다시 m_lipool 생성풀에 넣어준다.


C#) 가비지 컬렉터 / 유니티) 가비지 컬렉터


가비지컬렉션?

메모리 관리 기법중 하나, 프로그램이 (동적)할당했던 메모리에서 필요없게 된 영역을 해제하는 과정이다.


명시적 해제가 필요없다

 C++에서는 개발자가 직접 메모리할당을 해제를 해야하는데 유니티에서 사용하는 C#프로그래밍에서는 가비지 컬렉터가 존재하기  명시적 메모리해제를 하지 않아도 된다. 또,유니티 Mono엔진 런타임 시스템 덕분에 메모리 노출 가능성도 크게 준다고 한다.


언제 발생?

힙을 할당해야하는데 사용가능한 메모리가 충분하지 않다고 판단될때, 그리고 아무도 모르는 시점(플랫폼마다 다르다고 한다.)


알 수 없는 메모리 해제 시점

가비지콜렉터는 메모리를 해제 할때 작업을 수행하는 데 시간이 소요된다. 메모리의 양과 프로그램을 실행 중인 플랫폼에 따라 다르고, 중요한 것은 메모리가 해제되는 시점을 개발자가 알 수 없다는 것이다.  게임과 같은 실시간으로 실행되는 프로그램에서는 큰 문제가 될 수 있다.


가비지컬렉터가 실행되면 동반되는 것들

힙에 할당된 변수가 스코프를 벗어나면, 사용되었던 메모리는 이때 해제 되지 않고, 가비지 컬렉터가 실행되어야 해제 되는 이이 때 일어나는 과정들이 있다. 

가비지 컬렉터가 힙 상의 모든 오브젝트를 검사하는데 오브젝트 참조값을 모두 확인해서 해당 오브젝트가 힙상에서 스코프내에 있는지 확인하여 삭제 여부를 판단하다. 당연히 힙 상의 오브젝트가 더 많을수록 가비지 컬렉터는 할 일이 많아진다. 


힙 파편화

힙에 메모리가 저장될 때 데이터의 크기에 따라 블록 단위로 저장이 되는데 서로 다른 크기의 메모리 블록이 저장되고 해제되면서 남은 공간이 생기게 된다. 그렇기 때문에 실제 개발자가 사용하려했던 프로그램 메모리 사용량이 늘어나고, 메모리를 찾기 위해 가비지 컬렉터가 더 자주 실행하게 된다.

 


참고 : https://docs.unity3d.com/Manual/UnderstandingAutomaticMemoryManagement.html?_ga=1.214538258.405170267.1480455402

http://ronniej.sfuh.tk/optimizing-garbage-collection-in-unity-games-2/

http://ronniej.sfuh.tk/optimizing-garbage-collection-in-unity-games-1/


버텍스 및 프래그먼트 기초 셰이더 #2


월드 공간에 메시 노멀을 표시하는 셰이더


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    // no Properties block this time!
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // include file that contains UnityObjectToWorldNormal helper function
            #include "UnityCG.cginc"
 
            struct v2f {
                // we'll output world space normal as one of regular ("texcoord") interpolators
                half3 worldNormal : TEXCOORD0;
                float4 pos : SV_POSITION;
            };
 
            // vertex shader: takes object space normal as input too
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // UnityCG.cginc file contains function to transform
                // normal from object to world space, use that
                o.worldNormal = UnityObjectToWorldNormal(normal);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 c = 0;
                // normal is a 3D vector with xyz components; in -1..1
                // range. To display it as color, bring the range into 0..1
                // and put into red, green, blue components
                c.rgb = i.worldNormal*0.5+0.5;
                return c;
            }
            ENDCG
        }
    }
}
cs

노멀은 컬러를 만들어내는 것 외에도 모든 종류의 그래픽스 이펙트(조명,반사,실루엣 등등)에 사용된다.


#include "UnityCG.cgin" : UnityObjectToWorldNormal을 쓰기 위한 include


TEXCOORD0, TEXCOORD1 등은 텍스처 좌표 및 포지션과 같은 임의의 고정밀도 데이터를 나타낼 때 사용


UnityObjectToClipPos 오브젝트 공간의 버텍스를 화면으로 변환하는 유틸리티 함수

UnityObjectToWorldNormal :  객체 공간에서 화면으로 정점을 변형시키는 유틸리티 함수 


정규화된 벡터를 컬러로 시각화(-1.0~ +1.0 범위)

절반을 곱하고 나서 절반을 +


<적용 후>


월드 - 공간 노멀을 사용한 환경 반사


씬에서 반사 소스로 스카이박스가 사용 될 때 이 스카이박스 데이터를 담고 있는 

"디폴트"반사 프로브가 생성된다. (반사 프로브 : 내부적으로는 큐브맵 텍스처이다.)


큐브맵

__Cubemap__은 환경에 대한 반사를 나타내는 여섯 개의 사각형 텍스처 컬렉션입니다. 여섯 개의 사각형은 오브젝트를 둘러싸는 가상 큐브면을 형성합니다. 각각의 면은 월드 축의 방향을 따른 뷰를 나타냅니다 (위, 아래, 좌, 우, 앞, 뒤).

큐브맵은 오브젝트의 반사나 “주변 환경”을 캡처하는 데 사용됩니다. 예를 들어, skyboxes  environment reflections는 주로 큐브맵을 사용합니다.

유니티 도큐 : https://docs.unity3d.com/kr/current/Manual/class-Cubemap.html


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
 
            struct v2f {
                half3 worldRefl : TEXCOORD0;
                float4 pos : SV_POSITION;
            };
 
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // compute world space position of the vertex
                float3 worldPos = mul(_Object2World, vertex).xyz;
                // compute world space view direction
                float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                // world space normal
                float3 worldNormal = UnityObjectToWorldNormal(normal);
                // world space reflection vector
                o.worldRefl = reflect(-worldViewDir, worldNormal);
                return o;
            }
        
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the default reflection cubemap, using the reflection vector
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.worldRefl);
                // decode cubemap data into actual color
                half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
                // output it!
                fixed4 c = 0;
                c.rgb = skyColor;
                return c;
            }
            ENDCG
        }
    }
}
cs


mul(x,y)

: 두 행렬의 곱을 계산한다.

worldPos = mul(unity_ObjectToWorld, vertex).xyz;

월드공간 관측 방향 계산

worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos)); 

: 지정된 객체 공간 정점 위치에서 카메라 방향으로 월드 공간 방향을 계산하고 정규화 함

worldNormal = UnityObjectToWorldNormal(normal);

: 월드 공간을 노멀로 변환

reflect(-worldViewDir, worldNormal);

: 정반사광의 방향 벡터를 구하는 벡터반사 함수.

첫 번째 인자로 입사광의 방향벡터를 두 번째 인자로 반사면의 법선을 받는다.


UNITY_SAMPLE_TEXCUBE

 : 큐브맵을 샘플링하기 위한 빌트인 매크로

__unity_SpecCube0

: (셰이더 변수)사용중인 반사 프로브의 데이터를 포함하고있다

DecodeHDR 

: 반사 프로브 데이터에서 실제 컬러를 얻기 위함,반사 프로브 큐브맵을 특별하게 인코딩된 방식으로 저장


<적용 후>



노멀 맵을 사용한 환경 반영


노멀맵(Normal Maps)은 오브젝트들에 추가 지오메트리 생성 없이 추가 디테일을 생성하기 위해 종종 사용된다. 

노멀 맵 텍스처를 가지고 환경을 반사하는 셰이더를 만드는 예제


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
 
            struct v2f {
                half3 worldRefl : TEXCOORD0;
                float4 pos : SV_POSITION;
            };
 
            v2f vert (float4 vertex : POSITION, float3 normal : NORMAL)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(vertex);
                // compute world space position of the vertex
                float3 worldPos = mul(_Object2World, vertex).xyz;
                // compute world space view direction
                float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                // world space normal
                float3 worldNormal = UnityObjectToWorldNormal(normal);
                // world space reflection vector
                o.worldRefl = reflect(-worldViewDir, worldNormal);
                return o;
            }
        
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the default reflection cubemap, using the reflection vector
                half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, i.worldRefl);
                // decode cubemap data into actual color
                half3 skyColor = DecodeHDR (skyData, unity_SpecCube0_HDR);
                // output it!
                fixed4 c = 0;
                c.rgb = skyColor;
                return c;
            }
            ENDCG
        }
    }
}
cs


노멀맵을 사용하기 시작하면 표면 노멀 자체가 피셀당 기준으로 계산되어야한다. 

즉 환경이 픽셀마다 어떻게 반사되는지도 계산해야한다.

이전 쉐이더 예제코드와 크게 변한 것은 없다. 다른 점은 이 셰이더는 모델의 각 버텍스에 대해서가 아니라 화면의 모든 픽셀 각각 대해 계산을 한다. 


노멀 맵 텍스처는 대부분의 경우 좌표 공간에서 표현되는데 이는 모델의 "표면을 따르는"것으로 생각할 수 있다. 지금 만들려는 셰이더는 탄젠트 공간 기반 벡터를 알아야 하고 텍스처로부터 노멀벡터를 읽어야하고, 이 벡터를 월드 공간으로 변환하고 그 후 위 셰이더에서 모든 수학적 계산을 해야한다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
    Properties{
        // normal map texture on the material,
        // default to dummy "flat surface" normalmap
        _BumpMap("Normal Map", 2D) = "bump" {}
    }
        SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
 
            struct v2f {
                float3 worldPos : TEXCOORD0;
                // these three vectors will hold a 3x3 rotation matrix
                // that transforms from tangent to world space
                half3 tspace0 : TEXCOORD1; // tangent.x, bitangent.x, normal.x
                half3 tspace1 : TEXCOORD2; // tangent.y, bitangent.y, normal.y
                half3 tspace2 : TEXCOORD3; // tangent.z, bitangent.z, normal.z
                // texture coordinate for the normal map
                float2 uv : TEXCOORD4;
                float4 pos : SV_POSITION;
            };
 
    // vertex shader now also needs a per-vertex tangent vector.
    // in Unity tangents are 4D vectors, with the .w component used to
    // indicate direction of the bitangent vector.
    // we also need the texture coordinate.
    v2f vert(float4 vertex : POSITION, float3 normal : NORMAL, float4 tangent : TANGENT, float2 uv : TEXCOORD0)
    {
        v2f o;
        o.pos = UnityObjectToClipPos(vertex);
        o.worldPos = mul(_Object2World, vertex).xyz;
        half3 wNormal = UnityObjectToWorldNormal(normal);
        half3 wTangent = UnityObjectToWorldDir(tangent.xyz);
        // compute bitangent from cross product of normal and tangent
        half tangentSign = tangent.w * unity_WorldTransformParams.w;
        half3 wBitangent = cross(wNormal, wTangent) * tangentSign;
        // output the tangent space matrix
        o.tspace0 = half3(wTangent.x, wBitangent.x, wNormal.x);
        o.tspace1 = half3(wTangent.y, wBitangent.y, wNormal.y);
        o.tspace2 = half3(wTangent.z, wBitangent.z, wNormal.z);
        o.uv = uv;
        return o;
    }
 
    // normal map texture from shader properties
    sampler2D _BumpMap;
 
    fixed4 frag(v2f i) : SV_Target
    {
        // sample the normal map, and decode from the Unity encoding
        half3 tnormal = UnpackNormal(tex2D(_BumpMap, i.uv));
        // transform normal from tangent to world space
        half3 worldNormal;
        worldNormal.x = dot(i.tspace0, tnormal);
        worldNormal.y = dot(i.tspace1, tnormal);
        worldNormal.z = dot(i.tspace2, tnormal);
 
        // rest the same as in previous shader
        half3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
        half3 worldRefl = reflect(-worldViewDir, worldNormal);
        half4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, worldRefl);
        half3 skyColor = DecodeHDR(skyData, unity_SpecCube0_HDR);
        fixed4 c = 0;
        c.rgb = skyColor;
        return c;
    }
    ENDCG
}
    }
}
cs


학습참고

1) https://docs.unity3d.com/kr/current/Manual/SL-VertexFragmentShaderExamples.html

유니티 타일맵


유니티에서 제공하는 타일맵으로 맵을 쉽게 제작이 가능하다.




Grid

Grid : UI의 canvas같은 존재

Cell Size : 타일 하나의 크기


Tilemap Renderer

Sprite Renderer과 같은 기능


Tile Palette

타일을 모아놓은 콜렉션 에셋




타일을 클릭하고 붓을 칠하듯 찍으면 된다.

삭제 : Shit버튼을 누른상태에서 칠하거나 지우개툴을 선택

회전 : ‘[‘ , ‘]’ 버튼 이용




타일맵 충돌처리

타일맵전용 Collider가 따로 있음


콜라이더 합치기

이 파란색 영역역은 캐릭터가 닿지 않는 부분으로 Collider 각각 들어가 있는것은 비효율적이다


Composite Collider2D를 추가하고 TileMap Collider -> use by Composite를 체크해주면

콜라이더가 한개로 합쳐진다.

Rigidbody 속성을 바꿔줘야 맵이 떨어지지 않는다.


강의 참고 : https://youtu.be/dtpthaIYa8g?list=PL412Ym60h6uunDV_SaHfBXzfMhBjOFTnj


스프라이트 아틀라스


유니티에서 아틀라스는 텍스처를 한곳에 모은 한장의 큰 텍스처라고 할수있다.

텍스처 하나씩 따로따로 사용하고 관리하는것은 효율적이지 못하다.


아틀라스를 사용하면 드로우콜을 줄일 수 가 있다.


드로우콜?:

CPU가 GPU에게 어떠한 그림을 그려 달라고 요청하는것인데 많을 수록 게임이 무거워지고 프레임 저하가 올 수 도 있다.


프로젝트 셋팅하기

Unity 에디터 설정(Edit > Project Settings > Editor)에서 변경

빌드에 대해 활성화(Enabled for Builds): 플레이 모드에서가 아니라 빌드에 대해서만 패킹을 사용하고 싶을 때 

항상 활성화(Always Enabled): 패킹된 스프라이트가 플레이 모드 동안 스프라이트 아틀라스에서 텍스처를 확인하게 만들고, 편집 모드에서 원본 텍스처의 텍스처를 확인하게 만들고 싶을 때 사용




아틀라스 생성

Assets -> Create -> Sprite Atlas


이렇게 보라색 이미지가 생성된 것을 볼 수 있다.




이미지 패킹(묶기)!

그냥 드래그앤 드롭으로 끌어다 놓으면 된다.(폴더를 옮겨도 됨)

Type : Variant 다른 생성된 아틀라스를 포함시킬 수 있다.




스크립트 작성

using UnityEngine.U2D;

using UnityEditor.Experimental.U2D;


public class test : MonoBehaviour

{

public SpriteAtlas m_atlas;

public Image m_img1;


void Start()

{

m_img1.sprite = m_atlas.GetSprite("blue_boxCheckmark");

~~~생략

}

}




비교하기

<일반적인 Sprite를 넣었을때>


<아틀라스를 사용했을때>


Batches가 줄어든것을 볼 수 있다. 



룰타일 & 타일맵 레이어

    



룰타일

룰타일은 추가적인 다운로드가 필요하다.


https://github.com/Unity-Technologies/2d-extras

위 그림처럼 타일을 칠 할때 이미 설정해 놓은 룰에 맞게 칠 할 수 있다.




생성

Create - > Rule Tile

x : 해당 방향으로 타일이 칠 할수 없다

화살표 : 해당 방향으로 타일을 칠 할 수 있다.


생성한 룰타일은 TilePalets에 옮기면 생성이된다.


칠하기

룰의 맞게 색칠해 진다.



타일맵 레이어

타일맵에서도 레이어를 나누어 주변지물, 또는 배경을 표현할 수 있다.

(ex) 캐릭터 뒤에있는 나무 캐릭터 앞에있는 나무 통과는 되지만 캐릭터가 화면에 안보이는)


레이어 추가

Tile map 하나를 더 생성해주고

Tilemap Renderer -> Sorting Layer를 Background로 바꿔준다.

(칠하려는 타일은 색칠할 렌더러 속성을 받지 않고 칠하는 팔레트에 속성을 받기 때문에, 팔레트에서 Active Tilemap을 background를 수정해줘야한다.)


씬화면에서 Foucs On설정으로 타일맵만 볼수 있다.

유니티코리아  유튜브 타일맵 튜토리얼을 보고 작성



나무 -> Background

폭포 -> foreground


https://youtu.be/EZgvwvPcimQ

+ Recent posts