따뜻한 대한민국 겨울만들기 외규장각 도서 환수 모금 캠페인

 보통 쿼터뷰방식의 게임을 만들 때는 2d로 제작을 한다. 2d는 공간축이 x축과 y축 두가지 밖에 없기 때문에 객체의 멀고 가까움을 판단할 방법이 없다. 그렇기 때문에 실제로 그림을 그리기 위해서 필요한 공간좌표 외에 별도의 타일좌표를 가지고 있어야하고 이 타일 좌표로 멀고 가까움을 판단하고 객체를 그리는 순서를 계산하는 정렬등은 직접 구현해야 한다.

 삼차원공간(3d)은 축이 하나 더 있기 때문에 객체의 멀고 가까움을 판단할 수 가 있고 이를 통해서 객체를 그리는 순서의 정렬 과정은 렌더링 파이프라인 안에 있다. 따라서 따로 정렬을 구현할 필요가 없어진다. 이런 편의가 있기 때문에 unity3D를 이용해서 쿼터뷰 방식의 2.5d게임을 제작하려고 한다.

 하지만 정렬을 따로 구현하지 않아도 된다는 장점이 있다면, 내 마음대로 정렬 할 수 없다는 단점도 가지고 있다. 이런 문제로 3d공간에서 2.5d 게임을 만들 때 아래와 같은 문제가 생길 수가 있다.
 

뒤에있는 큰 건물이 작은 건물을 가린다


 화면에서 보이기에는 큰 건물이 작은 건물보다 뒤쪽에 있는데, 큰 건물이 작은 건물을 가리고 있다. 이렇게 보이는 이유는 정렬이 잘못되어서가 아니라 실제로 큰 건물의 빌보드가 작은건물의 빌보드보다 앞에 위치하기 때문이다.
 

빌보드의 위치좌표 보정 때문에 실제로 큰건물은 작은 건물보다 앞쪽에 위치한다


 이렇게 작은 건물의 빌보드보다 뒤에 있어야 할 큰 건물의 빌보드가 작은 건물보다 앞에 있는 이유는, 화면에 빌보드를 제대로 표현해주기 위해서 땅 위에 올라가는 빌보드의 위치를 보정해주기 때문이다.

 위치보정을 하지 않으면 아래처럼 건물의 빌보드가 땅과 겹쳐서 잘려서 보이는 문제가 생긴다. 이 때문에 건물 빌보드의 위치를 땅의 위치보다 위쪽으로 올려서 땅과 겹치지 않게 만든다.
 

잘려보이는 건물들


 빌보드는 크기가 모두 다르므로, 빌보드마다 보정값이 달라지게 되는데, 작은 빌보드는 조금만 위로 올려도 되고 큰 빌보드는 많이 올려야 땅과 겹치지 않고 정상적으로 보이게 된다.

빌보드를 옮기고 난 후 카메라로부터 거리가 바뀌게 되는 문제 발생한다


 렌더링 파이프라인에서의 정렬은 카메라와 물체의 거리를 가지고 하게 되는데, 큰 빌보드를 작은 빌보드보다 더 많이 올려주면서 카메라와 더 가까워지는 경우가 발생을 하고, 이 때문에 정렬의 순서가 바뀌게 되는 것이다.

 이런 문제를 해결하기 위한 방법으로 따로 타일좌표를 가지고 카메라와의 거리를 계산하고 이렇게 계산된 값으로 빌보드마다 RenderQueue의 값을 다르게 줘서 Draw순서를 강제로 바꾸는 방법을 생각했는데, 결과적으로보면 2d게임에서 타일좌표로 원근계산하는것과 같아져서 굳이 2.5d로 만드는게 무의미해진다.
  이외에도 다른 방법들을 생각해 봤는데, 계산이 복잡하거나 Scene위에서 빌보드들의 위치가 어정쩡해진다거나 하는 문제들이 있어서 시도하지는 않았다.

 몇달간 이 문제를 방치해놓았는데, 불현듯 머리를 스치는 생각이 하나 있었다. RenderQueue를 빌보드에 적용하는게 아니라 땅 객체에 적용을 하는 방법이다. 그동안 빌보드에만 RenderQueue를 적용해서 정렬의 순서를 바꿔야 한다고 생각했었다. 그런데 땅 객체에 RenderQueue를 적용해서 땅을 가장 먼저 그리게 만들면 굳이 땅 위로 빌보드를 올리는 위치보정 따위는 하지 않아도 되는 것이다.

정렬따위를 하지 않아도 되게 만드는 혁신적인 한줄



그리고 RenderQueue적용후 잘 정렬된 아래와 같은 결과를 볼 수 있게 되었다.

우왕ㅋ굳ㅋ



아.. 물론 모든 문제가 100% 해결된건 아니고 90%정도?-_-;;
저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

 3D를 하다보면 한번쯤 겪는 문제가 Alpha Sort(알파정렬)라고 한다.
 알파정렬은 투명한 영역을 가지고 있는 물체들을 그릴때, 정렬을 하는 기준과 실제 투명도를 가지고 있는 영역의 위치가 맞지 않기 때문에 생기게 된다.

 Unity에서도 이 문제는 빈번하게 발생을 하는데 대표적인 예가 아래의 예이다.



 잔디 모양의 바닥 타일을 깔고 그 위에 타일영역을 구분짖는 선을 텍스쳐로 만들어서 올렸는데 아래와 같이 이미지의 상단에 표시되는 녀석들은 격자의 아래에 있는것 처럼 보이게 된다.
 이런 문제가 생기게 된 원인은 정렬을 할 때 카메라로부터 오브젝트의 위치를 기준으로 잡기 때문이다.


  위에서 보는 것 처럼 객체를 그릴 때는 깊이 정렬을 통해, 카메라에서 가장 멀리 있는 녀석인 '1' 먼저 그리고 그리고 격자 오브젝트 '2'를 그리고 '3'을 그리게 된다.

 여기서 문제가 되는게 바로 '2'의 오브젝트 위치이다. 사실 이녀석은 월드 공간 전체에 누워서(?) 펼쳐저 있는 녀석인데 실제로 렌더링을 하기 위해서 정렬을 할 때는 이녀석의 가운데점인 '2'지점을 기준으로 정렬을 해 버리므로 '2'보다 뒤에 있는 녀석들이 먼져 그려저서 격자 아래에 있는것 처럼 보이게 되는 것이다.

 이와 같은 문제를 해결하는 방법중 하나가 RenderQueue를 이용하는 것이다.


    private void createGrid(){
       
        GameObject grid = (GameObject)Instantiate(Resources.Load("FBX/plane100"));

        BillBoard bComponent = (BillBoard)grid.GetComponent("BillBoard");
        if(bComponent != null){
            Destroy(bComponent);
        }
       
        grid.name = "Grid";
        grid.transform.localScale = new Vector3(320,320,320);
        grid.transform.position = new Vector3(0,0.1f,0);
        grid.transform.forward = Vector3.up;
       
        Material gridMaterial = new Material(Shader.Find("Particles/Alpha Blended")); // (1)
        grid.renderer.material = gridMaterial;
        grid.renderer.material.mainTexture = (Texture2D)Resources.Load("grid4");
        grid.renderer.material.mainTextureScale = new Vector2(100.0f, 100.0f);
       
        grid.renderer.enabled = true;
       
        print(grid.renderer.material.renderQueue);
       
        grid.renderer.material.renderQueue = grid.renderer.material.renderQueue-1; // (2)

    }

(1)에서 alpha Blending(알파블렌딩)을 하는 Shader(쉐이더)를 가지는 Material을 새로 만들어서 격자 오브젝트에 붙여주고, (2)에서 실제로 RenderQueue를 조절해서 다른 오브젝트들보다 먼저 렌더링을 하게 만들었다.
 보통은 Shader에서 RenderQueue를 지정하는 것 같은데 위의 (2)와같이 코드에서 지정해 줄 수도 있다.(어떤게 더 효율적인지는 모르겠다.)
 제대로 RenderQueue에 우선순위를 지정해줬다면, 아래와 같이 제대로 렌더링이 된 녀석을 확인할 수 있다.


 
 덤으로, RenderQueue와 관련해서 공식 문서에서는 아래와 같이 말하고 있다.

For special uses in-between queues can be used. Internally each queue is represented by integer index; Background is 1000, Geometry is 2000, Transparent is 3000 and Overlay is 4000.

자세한 내용을 확인하고 싶다면, (링크)를 눌러서 직접 확인해보자!!
저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

  1. 2010/11/09 15:22 Address Modify/Delete Reply

    비밀댓글입니다

    • 열혈양군 2010/11/16 21:02 Address Modify/Delete

      블로그 확인을 잘 안해서 답변이 늦었네요;;

      Texture2D는 GameObject로 바로 변환이 안되고
      Plane같은 GameObject를 하나 만들어서 material의 mainTexture 로 만들어 주시면 됩니다.

      iTween은 저도 아직 사용 안해봤는데 좋다고들 하더라고요.ㅎ

 Unity3D 예제를 보면 AssetBundle을 만드는 예제가 있는데 JavaScript 버전으로 짜여있고, 자세한 설명이 없기 때문에 내용을 한번에 이해하기 힘든 경우가 있다. AssetBundle에대한 이해를 돕고, 조금 더 쉽게 제작할 수 있도록 도움을 주고자 포스트를 작성한다.

 AssetBundle은 Unity3D에서 리소스를 효율적으로 사용하기 위해서 사용하는 리소스의 묶음이다. 안에는 텍스쳐(texture) 메시데이터(mesh), 메트리얼(material) 등 실제 게임에서 사용할 리소스들이 들어갈 수 있다. AssetBundle은 Unity Pro, 혹은 Unity iPhone Advanced 에서만 사용할 수 있다.

 Unity3D에서 AssetBundle을 많이 사용하는 가장 큰 이유는 로딩(다운로드) 용량을 줄이기 위해서이다. 특히 WebPlayer에서는 다운로드 용량을 줄이는건 절대적이다.

 쳅터 형식으로 구성되어있는 게임이 있다. 이 게임에 총 10개의 레벨이 있고 각각의 레벨은 1mb의 리소스들을 가진다고 가정하자. 사용자가 10개의 레벨이 모두 들어있는 파일을 다운받아서 플레이를 하는경우와, 각각의 레벨을 AssetBundle로 만들어놓고 사용자가 다음 레벨로 넘어갈 때 마다 AssetBundle을 다운로드를 받아서 로드하게 만드는 경우중 어느 것이 더 효율적일까?

 당연히 후자쪽이 될 것이다.

 사용자가 처음 게임을 시작해서 5개의 레벨을 진행했다고 가정하면, 전자의 경우는 사용자가 한번에 10개의 레벨 리소스를 모두 받아야 하므로 10mb를 한번에 받아야 한다.
 후자쪽은 1레벨을 할 때는 1레벨에 해당하는 리소스 1mb만 받고, 2레벨로 넘어갈 때 2레벨의 리소스에 해당하는 1mb만 추가로 받으면 된다. 이런식으로 사용자가 한번에 레벨 5까지 진행을 했을 경우 후자의 경우는 5mb만 다운로드 받게 된다. 전체적으로 받아야 하는 다운로드 용량이 전자에 비해서 크게 줄어들게 된다. 물론 모든 레벨을 한번에 진행하게 되면 다운받는 용량은 10mb로 비슷해질 것이다.

 이렇게 AssetBundle을 이용하면 리소스를 효율적으로 관리할 수 있게된다.

 그렇다면 AssetBundle은 어디서, 어떻게 만들까? 찾아본 사람들은 알겠지만, 메뉴어디에도 AssetBundle 이라는 단어는 보이지 않는다.
 자동으로 AssetBundle을 만들 수 있다면 참 좋겠지만, 안타깝게도 AssetBundle을 만드는 과정은 상당히 번거로운 과정으로 수작업의 연속이다. 간단한 AssetBundle을 만들어보자.

 먼저 프로젝트 폴더에 'Editor'라는 폴더를 만들어주자. 이 안에는 실제 게임과는 상관 없이 동작하는 녀석들이 들어가게 된다. 빌드를 해도 Editor안에 있는 녀석들은 빌드파일 안에 포함되지 않을것이다.(아마도) 'AssetBundle을 만드는 스크립트'도 게임과 상관없이 동작하는 녀석들이 되겠다. 스크립트를 새로 만들고 아래처럼 'Editor'폴더안에 넣어두자. 스크립트의 이름은 크게 중요하지 않다.(나의 경우는 C#스크립트로 만들었다.)




 새로  만들어놓은 스크립트를 열어보면 아무것도 작성되지 않은 깔끔한 상태의 스크립트를 볼 수 있다. 이제부터 해야할 일은 이 스크립트안에 AssetBundle을 제작하는 스크립트로 체워넣는 것이다. 일단 아래처럼 스크립트를 작성한다.
 
using UnityEngine;
using System.Collections;
using UnityEditor; //(1)

public class BuildAssetBundles{

    [MenuItem ("Assets/Auto Build Image File")] // (2)
//  @MenuItem("Assets/Auto Build Image File") // @ JavaScript

    public static void buildImage(){

        BuildPipeline.PushAssetDependencies(); // (3)

        BuildAssetBundleOptions  options = BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets; //(4)
       
        Object[] asset = new Object[3];
        asset[0] = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/a02-1.png"); //(5)
        asset[1] = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/a03-1.png");
        asset[2] = AssetDatabase.LoadMainAssetAtPath("Assets/Resources/b01-1.png");
       
        Debug.Log(asset[0]);
       
        BuildPipeline.BuildAssetBundle(null, asset, "Shared.unity3d", options);    //(6)
//        BuildPipeline.BuildAssetBundle(asset[0], null, "Shared.unity3d", options);    //(7)

        BuildPipeline.PopAssetDependencies(); //(8)
    }
   
}



 AssetBundle을 만드는 스크립트에는 (1)과 같이 UnityEditor 네임스페이스를 써야한다. 이 네임스페이스를 통해서 UnityEditor API를 사용할 수 있는데, (2)와 같은 부분이 UnityEditor API를 사용한 것이다. UnityEditor API를 사용하면 스크립트의 특정 코드를 Unity 의 메뉴에서 수행할 수 있게 만들어준다. 스크립트를 저장하면 'Assets' 메뉴에 'Auto Build Image File' 메뉴가 새로 생긴것을 확인할 수 있다. 이녀석을 눌러 AssetBundle을 생성할 수 있다. 아직 몇가지 할일이 더 남아있으니 성급히 누르지는 말자.

 



 (3)부분은 서로 다은 AssetBundle간의 의존성과 관계되어있다. AssetBundle을 여러개 만들때 상호간에 의존성을 주고 싶다면 BuildPipeline.PushAssetDependencies() 와 BuildPipeline.PopAssetDependencies()를 적절히 잘 사용해서 의존성을 만들어 줄 수 있다. 사용할 때 는 Push와 Pop의 개수가 같아야 한다고 한다. Unity3D공식 사이트에 있는 예제를 보면 보다쉽게 이해를 할 수 있다. (사실 아직은 명확히 이해가 안간다)

 (4)부분은 AssetBundle을 빌드할 때 옵션을 설정해주는 것인데, CollectDependencies 는 AssetBundle로 만들 Asset(리소스)에 의존성을 가지는 GameObject, Component등 모든 것들을 포함해서 같이 빌드 하는 것이고, CompleteAsset 같은 Asset안에 포함되어있는 GameObject, Component들을 포함해서 빌드하는 것이다. 사실 지금 하려고 하는 예제에서는 딱히 필요가 없는 옵션인것 같긴 한데, 일단 넣어두었다.

 (5)에서 실제로 리소스를 가져오는 작업을 한다. (파일 이름과 경로는 구미에 맞게 변경하자!)

 (6)에서 실제 AssetBundle을 만드는 작업을 수행하는데 (7)과 다른점은 (6)에서는 여러개의 리소스를 하나의 AssetBundle에 넣을 때 사용하고, (7)에서는 한번에 하나씩만 넣을 수 있다.

 이렇게 AssetBundle을 만드는 스크립트를 작성을 했으니 이제 해당하는 경로에 리소스를 위치해보자. Resource 폴더를 만들고 이미지 파일을 위치시켰다.
 

 이제 준비가 다 되었으니 'Assets'메뉴에 있는 'Auto Build Image File' 을 눌러보자.


위와같은 압축과정을 거치고 나면 아래와 같이 빌드된 AssetBundle 'Shared.unity3d'를 확인할 수 있다.


이렇게 만들어진 AssetBundle은 WWW클래스를 이용하면 언제든지 가져다 사용할 수 있다.

거듭 이야기 하지만, AssetBundle은 Unity Pro, 혹은 Unity iPhone Advanced 에서만 사용할 수 있다.

 
저작자 표시
크리에이티브 커먼즈 라이선스
Creative Commons License

댓글을 달아 주세요

  1. 별다래 2010/10/21 20:21 Address Modify/Delete Reply

    짱좋아요.~속시원하네

  2. Jincan 2011/02/10 16:15 Address Modify/Delete Reply

    어떻게이 사진의 파일에서 읽을 수 있지만, 정말 좋은, 당신의 기사 주셔서 감사합니다? 하지만, 다음 방법을 시도 영향
    ---------------------
    WWW www = new WWW ("file:///"+Application.dataPath+"/Shared.unity3d");
    yield return www;
    if(www.isDone)
    {
    Texture t=Instantiate(www.assetBundle.Load("Bucket2"))as Texture;
    //Instantiating a non-readable 'Bucket2' texture is not allowed! Please mark the texture readable in the inspector or don't instantiate it.
    }



    --

    • 열혈양군 2011/03/11 14:34 Address Modify/Delete

      can not understand you post well...
      but i know what about error message.

      you should check true 'Read/Write Enabled' Texture Importer.
      you can find that in Inspector.

  3. 무지 2011/11/09 20:57 Address Modify/Delete Reply

    와 와 와!!
    감사합니다. 오늘 하루죙일 해맸음 ㅠ.ㅠ

  4. darkkiss 2012/01/27 11:42 Address Modify/Delete Reply

    좋은 정보 감사합니다.
    담아 갑니다^^