프로그래밍/Unity2020. 4. 1. 23:42

 요즘 끄적끄적 만들고 있는게 옛날식 2.5D 구조를 가진 게임, 즉 공간은 3D 좌표계지만 거기에 2D 캐릭터를 수직으로 세워서 움직이는 형식의 게임이라, 1인칭 시점에서 이걸 조준하려면 사실 스파인 캐릭터의 2D 바운딩 박스가 3D로 검출이 되는가를 먼저 확인했었어야 했다. 하지만 내가 하는 게 언제나 그렇듯이 그런거 다 따지고 만드는게 아니라 일단 부딪쳐보고 거기서 필요한건 찾아가며 때우는 식이다 보니... 이게 그렇게 생각처럼 간단하게 되는게 아니라는 걸 알고 나서는 이미 때는 늦은 것이었다... 

 스파인의 바운딩 박스는 상당히 좋은 기능이다. 스파인 자체가 원체 UI가 간단명료하고 깔끔하고 빠르게 작업할 수 있는데다, 거기에 폴리곤을 적당히 집어넣고 본에 붙이면 바로 유니티에서 콜라이더로 변환되어 움직이고 ON/OFF도 된다. 이걸 유니티에서 하려 하면 노가다도 노가다고, 스파인 수정이 되면 유니티에서 리소스를 또 손봐야하고, 동작에 맞게 ON/OFF를 시키려면 스파인 애니에다가 anim 파일을 또 추가해야 하고 그럼 meta 파일도 또 생기고... 아무리 생각해도 유니티에서 별도로 콜라이더를 추가하는 건 하고 싶지 않았다. 그러나 아무리 발악을 해도 3D 레이캐스트가 스파인의 바운딩박스를 인식하질 못하니, 정말 이걸 해야 하나 싶으면서도 일단은 최후의 수단으로 남겨 놓고 다른 똘똘한 방법을 찾아보기로 했다. 

 


 원래는 조준점에다 레이를 붙여서 검출된 놈을 공격하면 되는 간단한 구조였는데 레이가 안 먹히니 문제가 커졌다. 차선책으로 생각한 건 일단 조준점의 X, Y 좌표를 써서 OnTrigger2D로 조준점에 걸린 유닛을 모두 찾아내고, 찾아낸 유닛들의 Z값을 전부 받아와서 그중에 제일 Z값이 작은 놈에게 공격이 맞는 식으로 하자는 거였다. 일단 논리적으로 말은 되는 알고리즘인데 실제로 짜자니 문제가 계속 발생했다. 내가 제대로 알고리즘을 못 짠 탓이겠지만, 가장 문제가 되는 건 조준점에 몇 놈이 겹칠지를 알 수 없다는 거였다. 사실 당연한 상황이다. 실제 게임이라면 조준도 움직이고 적들도 계속 움직일 거고, 조준점에 적 몇 놈이 겹칠 지는 아무도 모른다.

 이것 때문에 검색해보고 시도했던 방법이 대충 아래와 같다.

1) 조준점에 걸린 놈들을 List로 묶고 각각의 Z값도 List로 묶은 뒤 List.Min을 구해서 그 Z위치에 있는 놈이 어떤 놈인가를 구하기
2) 스파인 런타임의 BoundingBoxFollower를 뜯어보고 PolygonCollider2D를 생성하는 부분을 3D로 교체(...)
3) 적의 프리팹에 3D Plane을 심고 거기다 레이를 쏴서 일단 오브젝트 이름과 Z값을 알아낸 뒤 맞는 처리를 함

 1번과 3번은 일단 얼추 보기에 그럴듯한 아이디어이긴 했다. 실제로 제일 앞에 나와있는 놈이 어떤 놈인지 이름과 Z값을 알아내는 데까지는 성공했다. 문제는 여기서 총알을 쐈을 때였는데, 1)의 경우는 계속 실시간으로 갱신되는 List 안에서 계속 Min을 구한 뒤 그거에서 다시 OnTrigger2D를 하는 게 너무 복잡하고 어려웠다. 3)은 최전방 캐릭터를 알아내는 게 Plane이라서 문제였다. Plane을 작게 하면 Plane 밖에 나간 팔다리 등의 인식이 안되고, Plane을 크게 하면 화면상으로 팔다리 사이의 공간으로 뒤에 있는 적을 쏴도 앞에 있는 놈이 맞아버린다. 

 2번은 내가 스스로 생각해도 어이없는 발상이긴 했는데 평범하게 PolygonCollider2D를 PolyconCollider3D로 바꾸면 알아서 3D 콜라이더로 되지 않을까 하는 순진한 생각이었고, 결과적으로는 당연하게도 실패. 우선 PolygonCollider3D 따위는 없었다(...). 공식 런타임을 겁없이 손대려 했던 것도 좀 꺼림칙했는데, 에러 나는거 보고 얌전히 포기.

 이걸로 하루 반 정도를 보내고 나니 머리도 아프고, 영 해결될 가망이 없어 보여 그냥 유니티에서 콜라이더 노가다 해야 하나 싶어지던 참에, 구글 검색으로 찾은 글 하나가 아무래도 심상치 않아 보였다. 

https://answers.unity.com/questions/1087239/get-2d-collider-with-3d-ray.html?childToView=1713394#answer-1713394

 글이 약간 길긴 하지만 결과적으로는 Physics2D.GetRayIntersection 메소드를 쓰면 3D 레이캐스트로도 2D 콜라이더(바운딩 박스)를 검출할 수 있다는 내용이다. 조금 반신반의 하면서 적용해 봤는데 처음엔 실패했지만, 위의 1)~3)을 전부 시도해보고 나니 뭔가 좀 감이 오는 것 같아서 재도전했더니 상큼하게 성공.

 

 

 잘 안됐던 원인은 샘플 코드의 Camera.main.ScreenPointToRay(screenPosition)을 그대로 적용하려고 했기 때문이었다. 내 경우엔 조준이 카메라와 일치하지 않고 따로 놀기 때문에 레이 자체도 굳이 ScreenPointToRay 같은 걸 써서 카메라에 붙일 필요가 없었다. 평범하게 transform.position을 써서 조준에 붙였더니 잘 된다. 이걸 하고 나니 OnTriggerEnter2D, OnTriggerStay2D, OnTriggerExit2D로 장황하게 어설픈 체크 하는 코드도 필요가 없어져서 전부 삭제.

 

 뭔가 이거 만들면서 엄청나게 공부가 많이 되고 있는데 되게 뿌듯함... 

 

Posted by windship
프로그래밍/Unity2020. 3. 23. 23:36

1. 스파인 데이터 임포트

2. 바운딩박스가 잘 인식되는지 확인
  - Project의 스파인데이터(*_SkeletonData)를 클릭하고 인스펙터의 Slots를 펼친다음 Show Attachments를 체크하면 확인이 가능

3. 스파인 데이터에 본 구조를 표시함
  - 스파인 데이터를 씬에 불러와 넣은 뒤 인스펙터에서 Skeleton Animation (Script)에 있는 Add Skeleton Utility를 클릭
  - 아래쪽에 Skeleton Utility (Script) 가 추가됨. 이곳에서 아래쪽의 Spawn Hierachy를 클릭하고 Follow all bones를 클릭
  - 스파인 데이터에 root로 시작하는 본 구조가 표시됨

4.
  - 표시된 본 구조에서 원하는 바운딩 박스가 달려 있는 본을 찾아 클릭한 뒤 오른쪽 인스펙터에서 Skeleton Utility Bone (Script)에 있는 Bounding Boxes에 원하는 바운딩 박스가 표시되므로 클릭해줌
  - 스파인 데이터에 Polygon Collider 2D로 바운딩 박스가 변환되어 추가됨

5.
  - 추가된 Polygon Collider를 이용하기 위한 스크립트를 작성(boundingBoxToCollider)
  - 씬에 추가된 스파인 데이터의 원하는 바운딩 박스를 클릭하고 그 인스펙터에 위에서 작성한 스크립트를 끌어다 놓아 추가한다


Posted by windship
프로그래밍/Unity2020. 3. 23. 23:34

출처 : https://m.blog.naver.com/PostView.nhn?blogId=yoohee2018&logNo=220700356025&proxyReferer=https%3A%2F%2Fwww.google.com%2F

 

프리팹 이름으로 인스턴스화하기 / Resource.Load()

Resource.Load라는 함수를 이용하면 만들어놓은 프리팹을 이름으로 불러내서 게임 도중에 인스턴스화(생...

blog.naver.com

Resource.Load라는 함수를 이용하면 만들어놓은 프리팹을 이름으로 불러내서 게임 도중에 인스턴스화(생성)할 수 있다.

 

순서는 아래와 같다.

 

1) 프리팹 만들기

2) 프리팹을 리소스로 Import

3) 스크립트에서 Resource.Load()로 불러와서 Instantiate

 

Resource.Load()를 사용하려면 만들어놓은 프리팹을 리소스로 등록을 해야 하는데, Assets 폴더 아래에 Resources 라는 이름의 폴더를 만들고 (대소문자구별) 거기서 마우스우클릭 -> Import New Asset 클릭하여 Prefabs 폴더에 있는 프리팹 선택

 

리소스 폴더안에 하위폴더를 원하는대로 만들어서 관리할 수 있다.

나는 Prefabs라는 이름의 폴더를 하나 더 만들어서 import했다.

이렇게 리소스로 등록이 되었으면 아래와 같은 소스로, 이름을 가지고 리소스를 불러올 수 있다.

 

public void newObject(string objName)

{

Instantiate (Resources.Load ("Prefabs/"+ objName), new Vector3 (0, 0, 0), Quaternion.identity)

}

 

이렇게 하고 오브젝트 생성을 원하는 곳에서 위의 함수를 호출하면, 파라미터로 넘겨받은 objName을 가진 프리팹을 게임화면에 생성(인스턴스화) 할 수 있다. 프리팹이 수정될 경우 다시 import하거나 리소스 항목에서 함께 수정해 줘야 해서 이 점이 조금 번거로운 느낌이다. 그래서 지금 나는 프리팹을 스크립트에서 인스턴스화 시킬 때 ScriptableObject를 사용하고 있다.

Posted by windship
프로그래밍/Unity2019. 7. 15. 19:25

* 따라다니는 오브젝트 만들기

 

* 발사체 만들기

 

* 지형 유동적으로 변형시키기

 

* 커맨드 입력 시스템 만들기(일본어) #1, #2

 

* 사다리 만들기

 

* 컷씬 만들기(캐릭터 제어 일시 불가능하게 만들기) #1, #2

 

* 엑셀 데이터 불러다 쓰기

 

* JSON 파일 저장, 로드

 

 

Posted by windship
프로그래밍/Unity2018. 5. 6. 23:01
출처 : https://qiita.com/kuuki_yomenaio/items/00d74762e930d037ad27

Spine 오브젝트는?

Spine 개체는 Unity에서 말하는 MeshRenderer로 렌더링되고 있습니다. 
(spine-unity의 SkeletonRenderer.cs가 해당 소스입니다) 
즉, MeshRenderer의 Mask 처리를 만들 수 있다면, Mask를 사용할 수 있게 됩니다. 

이름이 설정되지 않았습니다 ..mov.gif
(왜 이런 엄한 부분을 가지고 예제를 만든 걸까...)

Mask 처리에 대해

요는 Renderer를 Mask하면 되는 것이지만, 
SpriteRenderer 자체를 Mask하는 처리는 Unity에는 없는 것 같습니다. 
그래서, 간단히 자작하기로 했습니다.

Shader로 실제 제작

간단한 것은 역시 스텐실 테스트를 사용하는 것이군요. 
아주 간단하게 설명하면, 스텐실이란 모양을 도려낸다는 의미로 
픽셀 렌더링을 할 때 이 점을 찍는가 찍지 않는다인지를 판정하는 테스트입니다. 
이것을 이용합니다.

Spine 쪽에 적용하는 shader

SpineShader.shader
Shader "Custom/SpineShader"{
Properties
{
        _MainTex ("Base (RGB)", 2D) = "white" {}
}

SubShader
{
        Tags {"Queue"="Transparent+2" "IgnoreProjector"="True" "RenderType"="Transparent"}
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        Stencil {
                                Ref 1
                                Comp Equal
        }

        Pass
        {
                CGPROGRAM
                        #pragma vertex vert
                        #pragma fragment frag

                        #include "UnityCG.cginc"

                        struct appdata_t
                        {
                                float4 vertex : POSITION;
                                float2 texcoord : TEXCOORD0;
                        };

                        struct v2f
                        {
                                float4 vertex : SV_POSITION;
                                half2 texcoord : TEXCOORD0;
                        };

                        sampler2D _MainTex;
                        float4 _MainTex_ST;

                        v2f vert (appdata_t v)
                        {
                                v2f o;
                                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                                o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
                                return o;
                        }

                        fixed4 frag (v2f i) : COLOR
                        {
                                fixed4 col = tex2D(_MainTex, i.texcoord);
                                return col;
                        }
                ENDCG
        }
}

}

Mask할 Sprite에 설정하는 Shader

SpineSoriteMask.shader
Shader "Custom/SpineSpriteMask"{
Properties
{
        _MainTex ("Base (RGB)", 2D) = "white" {}
}

SubShader
{
        Tags {"Queue"="Transparent+1" "IgnoreProjector"="True"}
        ZWrite Off
        AlphaTest Greater 0.5
        ColorMask 0
        ZTest Always


        Stencil {
                                Ref 1
                                Comp always
                                Pass replace
                        }


        Pass
        {
                CGPROGRAM
                        #pragma vertex vert
                        #pragma fragment frag

                        #include "UnityCG.cginc"

                        struct appdata_t
                        {
                                float4 vertex : POSITION;
                                float2 texcoord : TEXCOORD0;
                        };

                        struct v2f
                        {
                                float4 vertex : SV_POSITION;
                                half2 texcoord : TEXCOORD0;
                        };

                        sampler2D _MainTex;
                        float4 _MainTex_ST;

                        v2f vert (appdata_t v)
                        {
                                v2f o;
                                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                                o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
                                return o;
                        }

                        fixed4 frag (v2f i) : COLOR
                        {
                                fixed4 col = tex2D(_MainTex, i.texcoord);
                                if(col.a<0.1)discard;
                                return col;
                        }
                ENDCG
        }
}

}

해설

SpineShader.shader
   Stencil {
             Ref 1
             Comp Equal
   }
SpineSoriteMask.shader
   ColorMask 0
   Stencil {
             Ref 1
             Comp always
             Pass replace
   }

Stencil

해설에 대해서는 
edo_m18 씨의 [Unity] Unity의 Shader 스텐실 버퍼를 시도
를 참고하십시오.

먼저, SpineSpriteMask 입니다만, 
"참조 값 1의 것과 비교하고, 모두 OK로 해서,
참조 값을 버퍼에 기록하고, ColorMask 0 으로 묘화는 하지 않음" 
이 됩니다.

그리고 SpineShader. 
"참조 값은 1, 값의 일치를 체크" 
가 됩니다.

이 설정을 함으로써 
SpineShader 측의 묘화는  StencilTest 를 받게 되고, 
SpineSpriteMask 측에서 설정한 모양대로 도려내지도록 그려집니다.

Unity에서 사용할 때에는 Material화 시킬 필요가 있습니다만, 
그에 대해서는
github
여기를 참고하십시오.

---

* 스파인측 설정


* 마스크이미지측 설정

* 매터리얼 설정은 이렇게(특별히 이미지는 지정하지 않고 쉐이더만 넣음)



Posted by windship
프로그래밍/Unity2014. 12. 13. 23:11

1. 스크립트에 새 C# 스크립트 만들기 > PlayerMovement를 만든다.


2. 스크립트 내용을 다음과 같이 작성

using UnityEngine;

using System.Collections;


public class PlayerMovement : MonoBehaviour {


public float speed;

void Update () {


if(Input.GetKey(KeyCode.D)){

transform.Translate (Vector2.right * speed);

}

if(Input.GetKey(KeyCode.A)){

transform.Translate (-Vector2.right * speed);

}

if(Input.GetKey(KeyCode.W)){

transform.Translate (Vector2.up * speed);

}

if(Input.GetKey(KeyCode.S)){

transform.Translate (-Vector2.up * speed);

}

}

}


3. 씬에 필요한 프리팹을 넣고 그 프리팹의 Inspector에서 Add Component 버튼 > Scripts > 아까 만든 PlayerMovement 선택


4. Speed 값을 조정. 보통은 0.3 정도?


5. 플레이 ▶ 버튼을 눌러 실행시키면 해당 프리팹이 키보드 WASD 키로 상하좌우 이동이 가능하게 된다.

'프로그래밍 > Unity' 카테고리의 다른 글

Unity - 게임 제작용 테크닉 모음  (0) 2019.07.15
[Unity] Spine 오브젝트를 Mask하기  (1) 2018.05.06
유니티 셰이더 강좌 #6  (2) 2014.08.15
유니티 셰이더 강좌 #5  (0) 2014.08.15
유니티 셰이더 강좌 #4  (0) 2014.08.15
Posted by windship
프로그래밍/Unity2014. 8. 15. 16:53

출처 : http://jinhomang.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EC%85%B0%EC%9D%B4%EB%8D%94%EC%9D%98-%EA%B8%B0%EC%B4%88-6


---


 안녕하세요, 흑기사입니다.
 

 (개인휴가, 명절 연휴, 컨퍼런스가 연이어 발생하는 바람에 부득이하게 오랜 기간동안 글을 포스팅하지 못하게 되었네요…;)


 이전까지 유니티에서 셰이더를 제작하는 방법 중에서 fixed function shader를 작성하는 법을 알아보았습니다. 물론 동시에 ShaderLab의 기본을 익히는 시간도 가졌습니다. 고정 파이프 라인을 사용하는 fixed function shader의 특징을 다시금 짚어보자면:


     -버텍스 당 라이팅 연산이 이루어짐.
     -커스터마이징 불가.
     -하지만 구형 디바이스에서도 동작하는 장점이 있음.

 이 fixed function shader은 잘 사용하면 의외로 많은 쓸모가 있는 셰이더 작성 방식입니다. 따라서 많은 여러가지 예제를 접해보면 도움이 많이 됩니다. 이러한 예제들은 다음에 기회가 된다면 알아보는 시간을 가지도록 하겠습니다.  

 이번 시간에는 유니티에서 셰이더를 작성하는 또 다른 방법인 vertex/fragment program에 대해서 알아보겠습니다.

 앞서 고정 파이프 라인을 사용하는 fixed function shader의 특징을 짚어본 이유가 바로 이번에 다룰 셰이더는 프로그래머블 파이프라인을 이용하는 방식으로서 서로 대조적인 방식이기 때문입니다. 프로그래머블 파이프라인하면 가장 먼저 떠오르는 것은 '프로그래머블' 하다는 것입니다. 즉, 프로그래머가 그래픽 파이프라인에서의 버텍스 레벨과 프래그먼트 레벨(픽셀 레벨)에 각각 개입하여 자기 마음대로 커스텀한 함수들을 제작할 수 있다는 것입니다.
 사실 이것은 이미 예전부터 게임개발을 하셨던 분들에게는 아주 익숙한 셰이더 작성법입니다. vertex/fragment program 라는 타이틀보다는 'vertex&pixel shader' 라고 부르는 것에 더 친숙할 수도 있지만, 그냥 명칭의 차이일 뿐이며 동일한 것으로 생각하면 됩니다. 
 이러한 셰이더를 작성하기 위해서는 우리는 별도의 셰이더 언어를 사용하여야 합니다. 그런데 셰이더 언어는 한가지가 아닙니다. 예를 들어 OpenGL에 포함된GLSL, DirectX에 포함된 HLSL, NVIDIA에서 개발한 Cg 등이 있습니다. 이처럼 그래픽API를 어떤 것을 사용하느냐에 따라서 다른 셰이딩 언어를 사용해야 하는 경우가 발생할 수도 있습니다. (Cg의 경우는 OpenGL과 DirectX 모두에 호환됩니다.)

 자, 그러면 이러한 vertex/fragment program을 유니티에서 작성하기 위해서는 어떻게 해야 할지 알아보겠습니다. 그런데 그 전에 먼저 언급해야 할 것이 있습니다. 
 vertex/fragment program 자체의 제작 방식은 매우 일반적으로 언급되는 프로그래머블 커스텀 셰이더 작성법입니다.  이 글에서는 단지 유니티 엔진에서 이 셰이더 방식을 '적용'하려면 어떻게 해야 하느냐에 대한 것일 뿐,  vertex/fragment program의 근본적인 개념과 작성법을 설명하지는 않을 것입니다. 따라서 이에 관련한 프로그래머블 파이프 라인의 개념이나 셰이딩 언어의 문법 등에 대해서는 (혹시 필요한 분들은) 직접 학습이나 리서치하시길 권장합니다. 

 그럼 이제 정말 유니티에서 vertex/fragment program 를 어떻게 작성하는지 알아보겠습니다. 
 (여기서 우리는 vertex/fragment program의 제작을 위해서 Cg를 사용하도록 합니다. 그 이유는 나중에 다시 언급하도록 하겠습니다.)
 사실 우리가 실제로 필요한 것은 바로 버텍스 프로그램(버텍스 셰이더)와 프래그먼트 프로그램(픽셀 셰이더), 이렇게 두 개의 셰이더 함수를 Cg로 작성하는 것뿐입니다. 이 얘긴 결국 유니티 셰이더의 나머지 부분은 앞서 익혔던 ShaderLab 문법을 이용하여 그대로 작성할 수 있으며, 단지 버텍스/프래그먼트 셰이더 함수에 해당하는 부분만 Cg로 작성해서 끼워넣기만 하면 목표달성이라는 뜻입니다. 글로 표현하려니 뭔가 이상하군요.. 그냥 실제 코드를 보자면:

Shader "MyCustomShader" { 
     SubShader { 
       Pass { 
           // ...여러가지 pass관련 설정… 
            
           CGPROGRAM 
              // Cg code ... 
           ENDCG 


           // ...그밖의 pass관련 설정… 
       } 
    } 
}

 음, 이미 우리가 알고 있는 셰이더의 모습과 많은 차이는 없군요(다행입니다...).차이점이라면 CGPROGRAM과 ENDCG라는 키워드가 있는 부분이 새롭게 등장했다는 것입니다. 아마 당연히 이 부분이 vertex/fragment program에 대한 것이라고 느껴지실 것입니다. 

 즉, 우리는 CGPROGRAM 와 ENDCG 키워드 사이에서 vertex/fragment program을 Cg언어로 작성할 것입니다. 이렇게 작성된 Cg조각은 유니티 엔진에 의해서 각각의 플랫폼들에 대해 적절히 동작하도록 자동컴파일됩니다. 이전에 익혔던 fixed function shader의 외형과 비교하자면, 단지 CGPROGRAM~ENDCG 블럭이 추가되었다는 것 외에는 크게 변한 점이 없습니다. 절대 복잡하지 않다는 점을 다시 한번 강조합니다. 
 

 이 블럭부분을 한번 자세히 들어가 볼까요.

 CGPROGRAM ~ ENDCG 블럭에는 순수 Cg코드 외에 여러 컴파일 지시자를 포함할 수 있습니다. 거의 기본적으로 포함되는 지시자는 바로 #pragma vertex 과 #pragma fragment 라는 지시자입니다:


     CGPROGRAM 


           #pragma vertex vertfunc 
           #pragma fragment fragfunc 

           // 여기에 Cg code
     ENDCG


 이것을 해석해보자면, vertfunc 라는 이름을 가진 함수가 버텍스 프로그램 (셰이더 함수)이며, fragfunc 라는 이름을 가진 함수는 프래그먼트 프로그램 (셰이더 함수)라는 것을 가리키는 것입니다. 그리고 이 vertfuc와 fragfunc 함수들을 Cg코드로 작성될 것을 의미합니다. 아주 간단합니다.


 그밖의 유용한 컴파일 지시자들 중에는 #pragma target, #pragma only_renderers, #pragma exclude_renderers등이 있습니다.


 #pragma target 은 어떤 버전의 셰이더 모델로 컴파일할 지를 지정할 때 쓰입니다. 

 유니티는 기본적으로 셰이더 2.0 버전에 맞추어 컴파일합니다. 만약 다른 버전으로 컴파일을 원한다면 아래의 예와 같이 지정할 수 있습니다:


     #pragma target 3.0 


이것은 셰이더 3.0 버전으로 컴파일을 하라는 뜻이겠죠.
 

 그리고 유니티는 지원가능한 렌더러들(d3d9, OpenGL 등등)에 대해서 모든 셰이더들을 컴파일하게 되는데요.  #pragma only_renderers와 #pragma exclude_renderes를 이용하여 특정 렌더러들에 대해서만 컴파일을 하거나, 특정 렌더러들에 대해서는 컴파일하지 않도록 할 수 있습니다. 현재 지원되는 렌더러들은 d3d9, d3d11, opengl, gles, xbox360, ps3, flash 입니다. 

 한가지 예를 들면: 


    #pragma exclude_renderers xbox360 ps3

이러면 xbox360과 ps3 렌더러에 대해서는 해당 셰이더에 대한 컴파일을 하지 않도록 합니다.


 그 밖에도 셰이더를 작성하다보면 여러 컴파일 지시자들을 사용하게 되는데요, 이들에 대해서는 그것들이 등장할 때마다 알아보도록 하겠습니다.

 그럼 오늘 언급한 내용을 모두 포함한 셰이더의 모습을 살펴보겠습니다:


Shader "MyCustomShader" { 
     SubShader { 
        Pass { 
           // ... 
            
          CGPROGRAM 
                #pragma vertex vertfunc 
                #pragma fragment fragfunc  
                #pragma target 3.0 
                #pragma exclude_renderers xbox360 ps3 
      
                 // add Cg code
          ENDCG 

          // ...
       } 
    } 
}


오늘은 여기까지입니다~
다음에는 유니티에서의 실제 간단한 vertex/fragment program의  예를 알아보도록 하겠습니다. 

감사합니다~


'프로그래밍 > Unity' 카테고리의 다른 글

[Unity] Spine 오브젝트를 Mask하기  (1) 2018.05.06
캐릭터 이동시키기(WASD)  (0) 2014.12.13
유니티 셰이더 강좌 #5  (0) 2014.08.15
유니티 셰이더 강좌 #4  (0) 2014.08.15
유니티 셰이더 강좌 #3  (0) 2014.08.15
Posted by windship
프로그래밍/Unity2014. 8. 15. 16:52

출처 : http://jinhomang.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EC%85%B0%EC%9D%B4%EB%8D%94%EC%9D%98-%EA%B8%B0%EC%B4%88-5


---


 안녕하세요, 흑기사입니다.

 이번 시간에는 오브젝트를 반투명으로 그려볼까 합니다. 지난 시간 마지막에 작성한 셰이더 코드를 약간 수정하여서 반투명 구(sphere)를 한번 그려보도록 하겠습니다.

 자, 우리가 오브젝트를 반투명으로 만들기 위해서 가장 먼저 해야 할 것은, 바로 블렌딩에 대한 디바이스의 렌더 상태를 변경해주는 것입니다. 그러니까 이 말은 앞으로 곧 렌더링할 오브젝트의 결과색상값과 이미 버퍼에 있는 색상값(배경색상)을 어떻게 섞을 지를 명시해줘야 할 필요가 있다는 뜻입니다. 만약 명시하지 않으면(지금까지와 같이), 블렌딩은 비활성화(디폴트)되어 있으므로 결국 오브젝트 렌더링 시에 결과 색상값을 그냥 버퍼에 덮어쓰므로 반투명을 표현할 수 없게 됩니다. 따라서 우리는 이것을 위해서 Blend 라는 ShaderLab 커맨드를 사용해서 렌더 상태를 블렌딩이 활성화되도록 지정해줘야 합니다:

 Blend SrcFactor DstFactor 

 위의 코드는 Blend 커맨드의 가장 기본적인 구문입니다. (다른  형식의 구문도 있지만, 여기서는 설명생략)
 이 구문을 해석하자면,
 (이제 나올 결과 색상값 x SrcFactor) + (이미 스크린(버퍼)에  존재하는 색상값 x DstFactor)
 이렇게 색상을 섞어서 최종 색상값을 결정하겠다는 뜻입니다. SrcFactor와 DstFactor 자리에는 우리가 직접 속성을 지정해주어야 하는데요. 사용할 수 있는 속성들은 아래와 같습니다:

 One, Zero,  
 SrcColor, SrcAlpha,  
 DstColor, DstAlpha,   
 OneMinusSrcColor, OneMinusSrcAlpha,  
 OneMinusDstColor, OneMinusDstAlpha 

 결국 우리는 이것들을 사용하여 블렌딩 연산을 지정하게 되는 것입니다. 뭔가 많아 보이고 복잡해 보이는데요... 사실 속성의 이름들을 하나하나 살펴보면 그 의미는 직관적으로 쉽게 알 수 있습니다. 어떤 세부 설명이 필요하다면, 유니티 메뉴얼을 통해 자세한 내용을 참조하실 수 있습니다. (일단 이번 글에서는 스킵해도 상관없습니다.)
 이제 당장 우리가 필요한 속성들만 취해서 구문을 작성해 보겠습니다. 우리는 아주 기본적인 알파 블렌딩을 하는 것이 목표이므로, SrcFactor에는 오브젝트 알파값을 지정하고 DstFactor에는 (1-오브젝트 알파값)을 지정하여야 합니다. 
 따라서 현재 오브젝트 알파값을 의미하는 SrcAlpha 와  (1-오브젝트 알파값)을 의미하는 OneMinusSrcAlpha를 속성 팩터로 사용하여 블렌드 구문을 작성합니다:

 Blend SrcAlpha OneMinusSrcAlpha 

 이 경우에 만약 오브젝트의 알파값이 0.6 이라고 한다면, SrcAlpha = 0.6 이고 OneMinusSrcAlpha = 0.4 이므로 오브젝트 색상과 배경 색상은 60% 대 40% 비율로 섞여서 결국 오브젝트가 반투명하게 보이는 효과를 나타내는 것입니다. 

 블렌딩 설정은 쉽게 끝났군요. 그 다음은... 
 아, 오브젝트 알파값을 지정해줘야 겠군요. 우리가 직접 오브젝트의 알파값을 정해주면 재밌을 것 같습니다.
 에디터에서 알파값을 지정할 수 있도록 하면 되겠군요. 이건 알파값을 지정할 수 있게 프로퍼티를 추가해주면 되겠죠. 
 이전에 Main Color 라는 프로퍼티를 추가했다가, 라이팅 연산을 없애면서 이 프로퍼티도 함께 삭제했었는데요. 알파값 지정을 위해서 다시 이 프로퍼티를 살리도록 합니다:

Shader "2Textures SemiTrans" { 
    Properties { 
        _Color ("Main Color", COLOR) = (1,1,1,1) 
        _MainTex("Texture"2D) = "white" {} 
        _SubTex("Texture"2D) = "white" {} 
    } 
    SubShader { 
        Pass { 
                 Blend SrcAlpha OneMinusSrcAlpha 
                
                 SetTexture [_MainTex] { 
                        Combine texture 
                 } 
                                                                       
                 SetTexture [_SubTex] { 
                        Combine texture lerp(texture) previous
                 } 
        } 
    } 
} 


자, 아직 코드가 완성되지 않았습니다. 
프로퍼티로 다시 추가한 _Color의 알파값을 사용하는 곳이 아직 없군요... 
이제 우리는 두번째 SetTexture 블럭을 다음과 같이 수정하려 합니다.

SetTexture [_SubTex] { 
          ConstantColor[_Color] 
          Combine texture lerp(texture) previous, constant 
}

 윽, 당황하지 마시기 바랍니다. 갑자기 뭔가 복잡한 코드가 추가되어 혼란스러울 수 있지만, 막상 내용은 별 것 아닙니다. 
 우선 Combine 커맨드를 주목하시기 바랍니다. 콤마(,)가 추가되었군요. 이건 대체 뭘까요? 
 Combine 커맨드에서 우리는 콤마(,)를 이용하여 색상값 연산과 알파값 연산을 분리할 수 있습니다. 분리한다는 말은 결국.. 콤마를 사용하지 않았던 이전에는 색상과 알파 연산이 동일하게 적용되었다는 얘기입니다. 그렇습니다... 만약 연산을 따로 콤마(,)를 사용하여 구분하지 않는다면, 색상(rgb)과 알파(a) 모두 하나의 Combine 연산을 통해서 계산합니다. (지금까지 그래왔겠군요.)
 그런데 우리는 임의로 직접 어떤 알파값을 지정해주길 필요가 있기 때문에, 일단 알파에 대한 연산을 따로 구분하기 위해 콤마(,)를 넣은 것입니다. 그 다음 알파연산 만을 위한 구문을 따로 작성했습니다. 위의 예제의 경우, 별다른 연산이 없이 constant 만 있는 걸 보니, 그냥 constant 라는 것을 알파값으로 사용한다는 의미겠군요. 여기서 그럼 constant는 또 뭘까요? constant는 단어 뜻 그대로 그냥 어떤 상수값을 말합니다. 그게 어떤 상수값인지는ConstantColor 라는 커맨드를 사용하여 지정합니다. 그러니까 ConstantColor (1,1,1,1) 이런 식으로 지정해주면, constant 값은 결국 (1,1,1,1)이 되는 셈이지요. 그런데 우리는 하나 더 나아가서 프로퍼티 _Color값을 알파값으로 이용하려 하므로 ConstantColor [_Color] 라고 지정한 것입니다. 결과적으로 constant는 _Color값이 되는 것이고, 마침내 우리는 constant의 알파값을 최종 알파값으로 사용하도록 지정했습니다.

지금까지 내용을 정리한 코드는 다음과 같습니다:

Shader "2Textures SemiTrans" { 
    Properties { 
        _Color ("Main Color", COLOR) = (1,1,1,1) 
        _MainTex("Texture"2D) = "white" {} 
        _SubTex("Texture"2D) = "white" {} 
    } 
    SubShader { 
        Pass { 
                 Blend SrcAlpha OneMinusSrcAlpha 
                
                 SetTexture [_MainTex] { 
                          Combine texture 
                 } 
                                                                       
                 SetTexture [_SubTex] { 
                          ConstantColor[_Color] 
                          Combine texture lerp(texture) previous, constant 
                 }
        } 
    } 
} 

그 결과는 이렇습니다:


 반투명이 제대로 적용했는지를 확실히 구문하기 위해서 더 먼 뒷쪽에 다른 두 오브젝트를 추가했는데요. 반투명 처리가 잘 적용되어 보입니다. 인스펙터 창에서 Main Color의 알파값을 조절해보면 구(sphere)의 반투명 정도가 변하는 게 느껴지는군요... 잘 구현된 것 같습니다!

 어라.... 근데 좀 들여다 보니까 약간 이상한 부분이 바로 눈에 들어옵니다!
 뒤쪽에 배치된 노란색 큐브를 주목해보면, 초록색 캡슐 오브젝트와는 달리 구(sphere)와 시야에서 겹치는 부분의 색상이 제대로 나타나지 않는다는 것을 쉽게 알 수 있습니다. 왜 이런 일이 생겼을까요? 
 이미 어느 정도는 벌써 눈치채셨으리라 생각이 듭니다. 우리는 지금 반투명 오브젝트를 그리는 중이고, 반투명 오브젝트는 일반적인 불투명한 오브젝트와는 다르게 그리는 순서에 큰 영향을 받지요. 그런데 위의 경우에 반투명 구  (sphere)를 일반적인 불투명한 오브젝트와 함께 그렸기 때문에 문제가 발생하게 되었던 것입니다. 어떤 오브젝트가 먼저 그려질 지 우리가 알 수 없기 때문입니다. 위의 결과로 볼 때에 노란색 큐브가 구(sphere)보다 늦게 렌더링 되어버려서 이슈가 생겨 버렸군요.
 이걸 해결하기 위해서는 모든 불투명 오브젝트들이 렌더된 한 후에 반투명 오브젝트를 렌더링되도록 하면 깔끔하게 정리가 됩니다. 
 이를 위해서 우리는 렌더링 순서 지정을 위해서 Queue 태그라는 것을 사용할 것입니다. 
 
 Queue 태그는 렌더링 순서를 결정하는 데에 사용하는 태그입니다.
유니티에서는 렌더링 순서를 구분하는 가장 일반적인 케이스를 고려하여, 다음과 같이 5가지의 큐를 미리 정의해 놓았으며, 아래의 큐 순서대로 렌더링을 하게 됩니다.

1) Background 
2) Geometry (디폴트) 
3) AlphaTest
4) Transparent
5) Overlay

 우리가 셰이더에 아무런 Queue 태그를 지정하지 않으면, 유니티는 해당 셰이더를 디폴트로 Geometry 큐로 지정합니다. 그런데 만약 필요하다면, 우리가 해당 셰이더에 5개 중 하나의 큐를 직접 지정해 줄 수 있습니다. 그렇게 되면 결국 렌더링 순서를 제어할 수 있게 되겠죠.
 지금 우리가 작성하는 셰이더의 경우, 반투명 오브젝트를 렌더링해야 하기 때문에 Geometry 큐보다 늦게 렌더링되는 큐로 지정해줘야 할 필요가 있습니다. 마침 적당한 큐가 눈에 띄는군요. 바로 Transparent 큐입니다. 이름대로 이 큐(queue)는 반투명 렌더링되는 경우를 위해 미리 정의되어 있는 큐(queue)입니다. 따라서 우리는 다음과 같이 ShaderLab의 Tags 구문을 이용하여 Queue 태그를 새로 지정할 수 있습니다:

 Tags { "Queue" = "Transparent" } 

이렇게 추가하면 우리는 반투명 구(sphere)를 다른 불투명한 오브젝트보다 더 늦게 렌더링하게 됩니다. 또한 Transparent 큐 안에서도 오브젝트들이 거리에 따라 내부적으로 정렬되므로, 더 이상 렌더링 순서에 따른 문제는 발생하지 않게 됩니다. 이렇듯 우리는 큐를 지정하여 렌더링 순서를 원하는대로 제어할 수 있습니다!

자, 그럼 다시 우리가 작성하던 코드로 돌아오면:

Shader "2Textures SemiTrans" { 
    Properties { 
        _Color ("Main Color", COLOR) = (1,1,1,1) 
        _MainTex("Texture"2D) = "white" {} 
        _SubTex("Texture"2D) = "white" {} 
    } 
    SubShader { 
  Tags { "Queue" = "Transparent}
        Pass { 
                 Blend SrcAlpha OneMinusSrcAlpha 
                
                 SetTexture [_MainTex] { 
                          Combine texture 
                 } 
                                                                       
                 SetTexture [_SubTex] { 
                          ConstantColor[_Color] 
                          Combine texture lerp(texture) previous, constant 
                 }
        } 
    } 
} 

 여기서 잠깐 하나만 더~.
우리는 Queue 태그를 지정할 때에 SubShader의 Tags를 사용했다는 것을 주의하시기 바랍니다. 이 언급을 하는 이유는 바로 ShaderLab 구문의 Tags는 SubShader 뿐만 아니라 Pass의 Tags도 있기 때문입니다. 그러니까 ShaderLab에는 SubShader Tags와 Pass Tags가 각각 존재하기 때문에 이 둘을 혼동하지 않도록 합니다. (Pass 태그는 나중에 배울 기회가 있을 것입니다.)

 그럼 이제 결과를 한번 보겠습니다:


이제 제대로 그려지는군요. 보기 좋습니다!

끝으로 Queue 태그에 대해서 하나만 더 이야기하고 마치겠습니다. 
앞서  유니티에는 미리 정의된 렌더링 큐(queue)가 5개 있다고 설명드렸고, 필요하면 다른 큐로 지정할 수 있다고 하였습니다. 그런데 이 렌더링 큐는 5개만 존재하는 것은 아니고, 필요에 따라서 직접 정의할 수 있습니다. 이 큐들은 유니티 내부에서 정수 인덱스로 표현됩니다. Background 는 1000, Geometry 는 2000, AlphaTest 는 2450, Transparent 는 3000 그리고 Overlay 는 4000 입니다. 그리고 큐(queue) 인덱스값 순서가 곧 렌더링 순서와 동일합니다. 따라서 우리가 만약 필요하다면 적당한 큐 인덱스를 정의하여 어떤 필요한 타이밍에 렌더링이 가능하도록 할 수 있습니다.
 예를 들면:

 Tags { "Queue" = "Geometry+1" }

 이렇게 정의하면 오브젝트는 2001번 렌더 큐에 속하게 되는 것이고, Geometry 큐(2000)와 AlphaTest 큐(2450) 사이에서 렌더링되는 것입니다.

 오늘은 여기까지 입니다.
 감사합니다~


'프로그래밍 > Unity' 카테고리의 다른 글

캐릭터 이동시키기(WASD)  (0) 2014.12.13
유니티 셰이더 강좌 #6  (2) 2014.08.15
유니티 셰이더 강좌 #4  (0) 2014.08.15
유니티 셰이더 강좌 #3  (0) 2014.08.15
유니티 셰이더 강좌 #2  (0) 2014.08.15
Posted by windship
프로그래밍/Unity2014. 8. 15. 16:52
출처 : http://jinhomang.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EC%85%B0%EC%9D%B4%EB%8D%94%EC%9D%98-%EA%B8%B0%EC%B4%88-4

---

 안녕하세요, 흑기사 입니다. 
 지난 시간에는 텍스처를 적용하여 fixed function shader를 어떻게 작성할 수 있는지를 알아보는 시간을 가졌습니다. 이번 시간에는 텍스처 활용에 대해서 좀 더 알아볼까 합니다.
 
 아! 갑자기 오브젝트에 텍스처 두 장을 입혀보고 싶은 생각이 드는군요. 별다른 이유는 없고, 그냥 문득 그런 생각이 떠올랐습니다; 그럼 한번 시작해볼까요?

 우리가 해야 할 일은 매우 명확합니다. 일단 텍스처를 위한 프로퍼티가 하나 더 추가하고, 텍스처를 적용하여 연산하기 위해 기존 SetTexture 외에 또 다른 SetTexture 커맨드를 추가해 주면 끝입니다.

 따라서 새롭게 작성된 셰이더 코드는:

Shader "2Textures" { 
    Properties { 
         _MainTex("Texture"2D) = "white" {} 
         _SubTex("Texture"2D) = "white" {} 
    } 
    SubShader { 
           Pass { 
               SetTexture [_MainTex] { 
                 Combine texture DOUBLE 
               } 
                                                 
               SetTexture [_SubTex] { 
                 Combine texture * previous 
               } 
           } 
    } 
}

어라... 근데 뭔가 예전 코드에 비해 갑자기 허전해졌습니다!
네, 그렇습니다... 이번에는 라이팅 연산은 제외하고 순수하게 텍스처만 합성하여 오브젝트에 적용해볼까 합니다. 따라서 재질과 라이팅에 관한 코드는 과감하게 삭제하였습니다. 혼란스러워 하지 마시길 바랍니다.(오히려 코드는 더 간단해졌습니다!)

 코드를 한번 살펴보도록 하겠습니다. 진한 글씨로 표시된 부분을 주목할 필요가 있군요. 일단 라이팅 연산을 배제했기 때문에 기존에 SetTexture 연산을 정의할 때 사용하던 primary 가 없어졌군요. 이전 결과값이 없으므로, 그냥 첫번째 텍스처 색상값만 두 배 연산하도록 했습니다. 그리고 다음에 등장하는 두번째 SetTexture 커맨드를 보시면 뭔가 새로운 것이 등장했습니다. 바로 previous 라는 녀석인데, 이것은 바로 직전의 SetTexture에서의 연산를 통해서 나온 결과값을 의미합니다. 따라서 지금 코드에서는 previous 는 앞서 _MainTex 색상값을 두배한 값을 가리키게 됩니다.

이번에는 인스펙터창을 살펴볼까요? 
우리는 여기서 서로 다른 2장의 텍스처를 설정하게 됩니다:

그래서 적용 결과는:

적절히 두 이미지가 합성되어서 나오는군요...  

음, 근데 이번에는 생각보다 그렇게 재밌지는 않았군요. 그럼 다른 것을 하나 더 해보겠습니다.

 이번에는 벽돌 텍스처 이미지 대신, 아래의 캐릭터 이미지를 덧붙여 보겠습니다. 

 이전과는 상황이 좀 다릅니다. 이번에는 검정부분은 적용되어선 안되고, 캐릭터 부분은 적용하되 이전 텍스처 이미지와 섞이지 않도록 합니다. 쉽게 다시 말하면, 캐릭터 모양의 스티커를 떼어서 구(sphere)에 붙인다고 생각하면 되겠습니다.


 만약 앞서 작성한 셰이더 코드를 그대로 사용하면 어떤 결과가 나올까요?
 결과는 이렇게 됩니다:

당연하겠지만.. 원치 않은 결과가 나왔습니다. 단순히 텍스처 색상값을 곱했기 때문에 캐릭터는 이전 텍스처 이미지와 섞여버렸고, 출력하고 싶지 않은 검정부분은 그대로 그려질 수 밖에 없군요. 이거 뭔가 다른 조치가 필요하다는 것이 느껴집니다.


우선 우리는 원하는 것을 얻기 위해서 캐릭터 텍스처의 알파값을 사용하기로 합니다. 사실 지금 사용하는 캐릭터 텍스처 이미지의 알파값을 확인해보면 다음과 같이 제작되었습니다:

그러니까 캐릭터 부분은 알파값이 1 이고, 다른 검정색으로 칠해진 부분은 알파값이 0 이었군요. 음, 그렇다면 해당 알파값의 비중만큼 해당 캐릭터 텍스처의 색상을 반영하도록 한다면 원하는 결과를 얻을 수 있을 것 같습니다. (즉, 알파값이 0인 경우에는 캐릭터 텍스처 색상값을 적용하지 않게 되겠죠.)


이것을 위해 마지막 SetTexture 커맨드의 블럭의 코드를 다음과 같이 수정하겠습니다:

 SetTexture [_SubTex] { 
       Combine texture lerp(texture) previous 
 } 

lerp() 라는 것이 등장했군요. 음, 뭔가 어려운 듯이 보이지만 알고보면 간단한 구문입니다:
 
 A lerp (B)  C
 
A와 C를 보간하여 결과값을 만들게 되는데, 그 기준값으로 B의 알파값를 사용하겠다는 것을 의미합니다. 즉, B의 알파는 0~1의 범위를 가지며 B = 1일 경우엔 결과는 A가 되며, B = 0일 경우에 결과는 C가 됩니다. 따라서 이번 예제에서는 캐릭터 텍스처의 알파값을 이용하여, 적절히 두 텍스처의 색상값을 취하여 원하는 결과를 얻어낼 수 있는 것입니다.

결국 최종 코드와 결과는:

Shader "2Textures" { 
    Properties { 
         _MainTex("Texture"2D) = "white" {} 
         _SubTex("Texture"2D) = "white" {} 
    } 
    SubShader { 
           Pass { 
               SetTexture [_MainTex] { 
                 Combine texture
               } 
                                                 
               SetTexture [_SubTex] { 
                 Combine texture lerp(texture) previous 
               } 
           } 
    } 
}

이번 시간은 여기까지입니다.

감사합니다~



'프로그래밍 > Unity' 카테고리의 다른 글

유니티 셰이더 강좌 #6  (2) 2014.08.15
유니티 셰이더 강좌 #5  (0) 2014.08.15
유니티 셰이더 강좌 #3  (0) 2014.08.15
유니티 셰이더 강좌 #2  (0) 2014.08.15
유니티 셰이더 강좌 #1  (0) 2014.08.15
Posted by windship
프로그래밍/Unity2014. 8. 15. 16:44

출처 : http://jinhomang.tistory.com/entry/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EC%85%B0%EC%9D%B4%EB%8D%94%EC%9D%98-%EA%B8%B0%EC%B4%88-3


---


지난 시간에는 마지막에 fixed function shader 에 대해서 짧게 언급한 후 마무리 했었는데요..
 조금만 더 이야기 해보겠습니다.
 앞서 이미 얘기한 대로 fixed function shader는 이름에서도 느껴지듯이 고정기능 파이프 라인을 사용합니다. 이미 아실테지만, 원래 셰이더를 작성한다고 하면 당연히 프로그래머블 파이프라인을 떠올리는 것이 상식이죠. 즉, GPU의 버텍스 레벨과 픽섹 레벨 단계를 우리가 마음껏 코딩(버텍스셰이더, 픽셀셰이더)하여 원하는대로 멋대로 구현하는 것에 진정한 의미가 있습니다. 
 그런데 지금 현 시점에서 우리는 클래식한 고정기능 파이프 라인을 이용한 셰이더(fixed function shader)를 작성하고 있는 중입니다.
 이전 마지막 코드를 살펴볼까요,
Shader "VertexLit White" {  
    SubShader {  
        Pass {  
            Material {  
                Diffuse (1,1,1,1)  
                Ambient (1,1,1,1)  
            }  
            Lighting On  
        }  
    }  
}
 다시 들여다보니 정말 프로그래밍을 했다고 할 수 있는 부분은 보이지 않네요. 
 전부 그냥 무언가 제공되는 것에 대한 '설정'만을 했을 뿐입니다. 재질의 값을 설정하고 라이팅 기능을 그냥 켰을 뿐이고...음.. 고정기능 파이프라인을 사용한 것이 확실하군요... 역시 제대로 셰이더 코드를 작성했고 내세우기는 어려워 보이네요.
 게다가 고정기능 파이프라인에서는 버텍스당 라이팅(per-vertex lighting)을 사용합니다. 즉, 라이팅 연산이 버텍스 레벨에서 이루어진다는 것이죠. 따라서 픽셀 레벨에서의 라이팅 연산에 비해서는 연산 부하는 적겠지만, 당연히 그만큼 품질이 아주 떨어집니다. 더욱 슬픈 건 무엇인가를 개선하거나 수정하기 위한 프로그래머블한 방법은 없다는 것입니다. 고정기능 파이프라인이기 때문이지요!... 결국 할 수 있는 게 별로 없는 셰이더군요.    
그렇다면 fixed function shader를 사용하는 것은 진짜 쓸모없는 일일까요? (그럼 지금까지 뭘 한거죠.. ;)

 결론부터 말하자면 그렇지는 않습니다. 다행히 쓸모가 있습니다. 
 fixed function shader는 다양한 하드웨어에 대응에 꼭 필요합니다. 이미 예전에 언급한 적 있지만 우리는 다양한 플랫폼과 다양한 디바이스 환경에 대해 대응해야 합니다. 그러기 위해서 fixed function shader와 같은 저레벨 셰이더(?) 작성이 필요한 것이죠. 특히 오래된, 후진 그래픽 하드웨어 사양에 대한 fallback 셰이더로써 사용되는 데 아주 유용합니다. 유니티 빌트-인 셰이더들을 살펴보시면 아시겠지만, 최종적인 fallback은 fixed function shader를 두고 있습니다. 따라서 어떠한 디바이스에서도 대응할 수 있게 끔 구성해 놓은 것이죠. 
 그리고 품질을 대한 것은 논외로 하더라도 간단한 기능의 셰이더들은 fixed function shader로 제작하는 것이 더 수월할 수 있습니다. fixed function shader는 ShaderLab으로만 구성되기 때문에 쉽고 간결하게 깔끔히 작성할 수 있기 때문이죠. 그래서 알아두면 손해보지는 않을 거라고 생각이 됩니다.
 
지금까지 fixed function shader에 대한 이야기를 잠깐 해보았는데요...
앞으로 당분간은 ShaderLab만을 이용하여 제작할 수 있는 이 fixed function shader 에 대하여 계속 집중해보겠습니다.

 그럼 돌아가서 마지막에 작성한 셰이더를 다시 살펴봅니다.

Shader "VertexLit White" {  
    SubShader {  
        Pass {  
            Material {  
                Diffuse (1,1,1,1)  
                Ambient (1,1,1,1)  
            }  
            Lighting On  
        }  
    }  
}
 아, 우리가 이전에 구(sphere)에 적용했던 기억이 납니다. 조명에 영향받은 흰색 공을 화면에 띄웠었죠...
 근데 문득, 구(sphere)를 빨간색으로 해보면 더 멋질 것 같은 생각이 듭니다.
 그러려면 간단히 코드에서 재질의 Diffuse, Ambient 색상값을 변경하면 해결되겠군요:

Material {  
      Diffuse (1,0,0,1)  
      Ambient (1,0,0,1)  
}

아주 간단합니다.
그런데 이렇게 하면 재질 색상이 셰이더 항상 고정이 되버리네요. 여러 색상을 원할 경우, 그 색상 수만큼 셰이더를 만들고 각 오브젝트에 적용해야 한다는 얘기가 되네요. 이건 매우 비효율적일 수 밖에 없군요.
따라서 우리는 색상값을 고정시키지 말고 외부(유니티 에디터)에서 그 값을 읽어와서 셰이더에서 사용할 수 있도록 하는 것이 올바른 방법인 듯 합니다.  이것을 위해서 유니티는 Properties 구문을 제공합니다. Properties 안에 셰이더 외부로부터 개발자에 의해 설정될 수 있는 파라미터들을 정의하게 됩니다. 
 그러면 우선 코드에 Properties 구문을 추가해보겠습니다:

Shader "VertexLit Simple" { 
    Properties { 
        _MyColor ("Main Color", COLOR) = (0,0,1,1
    } 
    SubShader { 
        Pass { 
            Material { 
                Diffuse [_MyColor] 
                Ambient [_MyColor] 
            } 
            Lighting On 
        } 
    } 

위의 코드를 보면 새롭게 Properties 블럭이 추가된 것을 보실 수 있습니다. 1개의 파라미터 변수가 추가되었군요.
이 부분만 살펴보자면:

_MyColor ("Main Color", COLOR) = (0,0,1,1)

 _MyColor는 속성 변수의 이름을 나타내고, "Main Color"는 에디터 상에서 보여질 라벨(이름)입니다. COLOR는 변수가 색상타입임을 의미하며, 에디터 상에서 디폴트 값은 파란색(0,0,1,1)으로 지정함을 의미합니다.

 이렇게 Properties 블럭이 추가됨에 따라서 에디터에는 어떤 변화가 일어났는지 한번 보겠습니다.

재질 인스펙터 창에 뭔가 한 줄 추가된 것이 보이네요. "Main Color"라는 색상을 설정할 수 있는 인터페이스가 생겼고, 디폴트 색으로 현재 파란색이 설정되었습니다. 앞서 Properties 블럭에 추가한 내용이 반영되었다는 것을 알 수 있습니다. 예상한대로 에디터의 "Main Color" 값을 변경하면,  셰이더의 _MyColor 값에 그 값이 적용됩니다.

 그럼 이제 이 값을 사용하려면, 셰이더에서는 어떻게 작성해야 할까요?
 셰이더 내부에서는 [ 프로퍼티 이름 ] 이라고 작성하여서 그 값을 사용할 수 있습니다.
 그냥 위 코드의 일부분을 보면 알 수 있습니다:

 Material { 
           Diffuse [_MyColor] 
           Ambient [_MyColor] 
          }

 재질의 Diffuse와 Ambient 색상을 (r,g,b,a) 에서 [_MyColor]로 수정하였군요. 즉,  _MyColor 값을 참조하여  색상값을 지정하겠다는 것이죠.
 자, 드디어 에디터로부터 언제든 원하는 색상으로 오브젝트의 색상을 지정할 수 있게 되었습니다.
 물론 여기에서 사용한 COLOR 타입 프로퍼티 외에도 다양한 타입의 프로퍼티들이 존재하며, 셰이더에서는 그것들을 사용할 수 있습니다. 나머지 프로퍼티 타입들은 앞으로 계속 등장할테니 그때마다 익히면 되겠습니다.


 이제 조금 더 욕심이 생기는군요. 이젠 단순히 재질의 색상을 정하는 것 만이 아니라, 이제 표면에 텍스처 이미지를 입혀보려 합니다. 이것은 ShaderLab의 SetTexture를 이용하면 됩니다. 
 그전에 먼저 우리는 사용할 텍스처를 에디터에서 설정할 필요가 있습니다. 그러기 위해서 방금 전에 배웠던 프로퍼티를 활용하면 됩니다. Properties 블럭 안에 다음과 같이 프로퍼티 변수를 하나 추가하겠습니다.

_MainTex ("Base Texture"2D) = "white" {}

 벌써 새로운 프로퍼티 타입이 등장했군요. 텍스처를 위한 2D 타입입니다. 디폴트 값으로 "white"가 지정되어 있는게 보입니다. 이것은 만약 개발자가 에디터를 통해 텍스처 이미지를 설정하지 않았을 때, 이 값을 어떻게 취급할 지를 결정합니다. "white"니까 흰 색상값으로 취급하겠군요. 이외에도 "black", "gray", "bump" 를 설정할 수 있으며, "" 도 가능합니다.
 { } 안에는 별도의 옵션이 들어갑니다. 이 옵션에 관해서는 이번에는 다루지 않고, 다음 번에 기회가 될 때 다루도록 하겠습니다.

에디터는 다음과 같이 변화가 생겼습니다:

Texture 프로퍼티가 추가된 것을 확인할 수 있군요. (텍스처는 임의로 맨홀 뚜껑 이미지를 세팅한 상태입니다.)

 그럼 이제 에디터에서 설정한 텍스처 이미지를 사용하기 위해서, 앞서 잠깐 언급된 SetTexture 라는 커맨드를 사용해보겠습니다. 한 가지 유의할 점은 우리가 텍스처를 적용하는 것은 색상을 결정하는 최종단계에서 이루어지므로, 코드의 마지막 부분에 들어가야 한다는 것입니다. 어쨋든:

SetTexture[_MainTex]

이렇게 추가하면, 우리가 에디터에서 설정한 _MainTex 를 가져와 적용하게 됩니다.

그럼 코드는:
Shader "VertexLit Simple" { 
    Properties { 
        _MyColor ("Main Color", COLOR) = (1,1,1,1
        _MainTex ("Base Texture"2D) = "white" {} 
    } 
    SubShader { 
        Pass { 
            Material { 
                Diffuse [_MyColor] 
                Ambient [_MyColor] 
            } 
            Lighting On 

            SetTexture [_MainTex]  
        } 
    } 

적용한 결과는:


그런데 뭔가 이상하게 보입니다. 라이팅 연산이 반영되지 않은 것도 같고...
아, 그러고보니 그냥 밋밋하게 최종 렌더링 색상으로 텍스처 이미지 색상 만이 적용되어 버렸네요.

Pass의 끝부분에 단순히 SetTexture [프로퍼티] 만 작성해놓았더니, 이전에 재질과 빛이 라이팅 연산을 통해 나온 결과 색상값은 그냥 무시해버리고, 현재 텍스처로 덮어씌워지는 현상이 발생했군요.

사실 우리는 텍스처를 설정하면서, 이전에 계산된 결과 색상값과 현재 텍스처가 어떻게 결합할 지를 결정해야 합니다. 그래야 적절히 믹스되어서 그럴듯한 색상으로 최종값이 결정되는 것이지요. 일단 다음과 같이 다시 코드를 추가해보겠습니다.

SetTexture [_MainTex] { Combine texture * primary } 

SetTexture 커맨드에 새로운 블럭을 추가되었습니다. 블럭 안에는 어떻게 색상을 결합할 지를 정하는 코드가 들어가며, Combine 커맨드로 설정하게 됩니다. texture는 현재 설정한 텍스처의 색상을 의미하고,  primary는 이전 라이팅연산되어 나온 색상을 의미합니다. 그러므로 이전 결과값과 현재 텍스처를 곱하겠다는 얘기로 해석되는군요. 
근데 여기서 조금 더 코드를 추가해볼까요.. 조명 강도를 좀 높이기 위해서 결과값을 두 배로 만들어주게끔 DOUBLE 을 추가합니다.DOUBLE은 결과값을 x2 해주는 키워드입니다.

SetTexture [_MainTex] { Combine texture * primary DOUBLE }


그럼 이제 최종 코드는:
Shader "VertexLit Simple" { 
    Properties { 
        _MyColor ("Main Color", COLOR) = (0,0,1,1
        _MainTex ("Base Texture"2D) = "white" {} 
    } 
    SubShader { 
        Pass { 
            Material { 
                Diffuse [_MyColor] 
                Ambient [_MyColor] 
            } 
            Lighting On 

            SetTexture [_MainTex] { 
                    Combine texture * primary DOUBLE 
            } 
        } 
    } 

실제 적용해본 결과입니다:


자, 이번에는 여기까지입니다.
다음 시간에는 더더욱 그럴 듯한 셰이더를 작성해보도록 하겠습니다.
감사합니다~


'프로그래밍 > Unity' 카테고리의 다른 글

유니티 셰이더 강좌 #5  (0) 2014.08.15
유니티 셰이더 강좌 #4  (0) 2014.08.15
유니티 셰이더 강좌 #2  (0) 2014.08.15
유니티 셰이더 강좌 #1  (0) 2014.08.15
Unity 2D 스프라이트 - 버그 및 유의사항  (0) 2014.03.25
Posted by windship