레이블이 Tool인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Tool인 게시물을 표시합니다. 모든 게시물 표시

2013년 1월 10일 목요일

Subversion에서 Private Depot 사용하기(Mercurial을 이용)

Private Depot 얼마나 오랫동안 꿈꾸어 오던 것인지... -_-;

SVN과 같이 중앙 public depot을 사용하면 잘못된 코드를 체크인 했을때 그 때문에 피해를 입는 사람이 많다. 고로 제대로 작동안하는 코드를 집어넣으면 졸 욕을 쳐먹는다 -_-;

그래서 보통 코드가 제대로 돈다고 확인할때까지, 또는 코드를 보기 좋게 잘 다듬을 때까지 check-in을 안하곤 한다..... 이것의 문제는 한 1주일정도 체크인 없이 로컬 컴퓨터에서 계속 작업하는 경우가 있는데.. 그러다가 어느순간...

'아... 3일전에 만들어놨다가 제대로 안돌아서 지워버린 코드를 다시 쓰고 싶은데...'

라는 후회가 드는 경우가 있다는 것.... 이걸 미리 체크인 해놓았다면 단순히 버전 되돌리는 걸로 끝날 일... 중앙 depot시스템 때문에 못한거지... 퉤... - _-;

그래서 git이나 mercurial(hg)등의 DVCS (Distributed Version Control System)이 나름 각광을 받고 있었다. 하지만 게임개발을 하는 나에게 중앙서버가 없는 중구난방식의 분산 방식은 좀 별로였다. 내가 원한 것은 완전한 분산방식이 아니라.... 개인(private) depot에는 아무때나 내 맘대로 체크인 해놓고..... 나중에 준비가 되면 여태까지 해왔던 일들을 한번에 중앙 서버에 집어넣는 거였다. 한마디로 매일밤

"오늘까지 한 일. 어쩌구 저쩌구... 현재 컴파일은 안됨 ㅋㅋ"

이라는 commit log를 작성하고 싶었다고나 할까....

Perforce에서는 perforce sandbox를 사용하여 이 문제를 해결했었다. 하지만 Subversion을 사용할 때는 마땅히 어떻게 할 방법이 없었다. git-svn이라던가 hg-svn 등의 놈들이 있지만 이렇게 그다지 믿음이 가지 않았다. 난 모든간에 단순하고 간단한걸 좋아해서 이렇게 두가지 기능을 대충 합치는 프로그램을 안좋아한다. 사용하다가 말도 안되는 곳에 문제가 생겨서 골치만 아픈 경우가 너무 많거든...

하드디스크에 체크아웃 해둔 SVN 루트 디렉토리에 git이나 mercurial을 local depot를 만드는 법도 한 때 생각해봤으나.. 각 폴더마다 들어가 있던 .svn 폴더때문에 그것도 쉽지 않았다... 그래서 한동안 손을 들고 있었으나... 이게 왠일 SVN 1.7부터 폴더 구조가 바뀌었다. 이젠 루트폴더에 .svn폴더 하나만 넣는다.

이거 때문에 신나서 재빨리 시도해보았다.. 잘된다.. ㅠ_ㅠ

방법은 간단... 난 mercurial(HG)를 사용했다. 윈도우즈에서 git 쓸려면 SSH 설치며 뭐며 머리가 졸라 아픈데.. mercurial은 그냥 TortoiseHg만 깔면 된다.. 다른거 신경쓸거 하나도 없다...

모든간에 졸 간단한 방법:
  1. SVN checkout이 되어있는 root folder로 간다. SVN 버전이 1.7 미만이라면 1.7이상으로 업글한 뒤 현재 checkout 되어있는 소스트리를 최신버전으로 업데이트 해줘야 한다.
  2. 빈공간에 오른쪽 버튼을 누르고 TortoiseHg > Create Repository Here를 해준다.

  3. Init 대화창이 나오면. 뭐 다른 거 손 볼 필요 없다. 그냥 Create를 눌러준다.
  4. 이제 모든 파일이 제대로 보인다....

  5. 이제 private depot에 체크인 할때는 Hg Commit을... 중앙 depot에 체크인할때는 SVN Commit을 해주면 된다.
아, 정말 단순하고.. 깔끔한 방법이다.... 맘에 들어.. ㅠ_ㅠ

이 방법의 단점은 mercurial로 private depot에 체크인 했던 것은 changelist description을 자동으로 복사해올 방법이 없다는건데... 음.... 난 차라리 이 방법이 낫다. 매일 일하며 체크인 해 놨던 내용들을 쭈욱 살펴보면서 요약해서 적을 수 있으니까. 중앙서버까지 mercurial로 만들어 놓으면 local depot에 체크인 했던 changelist들이 전부다 개별적으로 submit된다. 맘에 안든다.... 뭐 collapse 익스텐션을 쓰면 이것도 해결 된다지만.. 역시 기본기능이 아닌 건 사람들이 그닥 많이 사용안하니 제대로 검증되지 않아서... 골치아픈 일이 많기 마련.... 따라서 extension은 그닥 쓰고 싶지 않아.....

어쨌든 이 방법 좋다... 만세~


2012년 2월 6일 월요일

유니티에서 topology가 같은 캐릭터간에 애니메이션 공유하기...

이미지 출처: http://answers.unity3d.com/questions/191282/sharing-animations-between-models.html

본(bone)의 길이가 다른 캐릭터들 사이에서 애니메이션을 공유하려고 한다고 생각해 보죠. 물론 각 캐릭터마다 애니메이션을 따로 만들어주면 좋겠지만 인력이 부족한다던가... 그런데 이렇게 본의 길이가 다른 캐릭터터들 사이에서 애니메이션을 공유할 수 있을까요? 뭐 되니까 이글을 쓰는거죠... -_-; 평행이동과 확대/축소를 사용하지 않으면 가능합니다. 달리 말하면 회전만 이용하면 된다는거죠. 이렇게 생각해보면 이해가 쉬워요. 본인의 오른쪽 팔꿈치를 안쪽으로 90도만큼 회전시켜 보세요. 이제 본인보다 키가 작은 친구에게 똑같이 오른쪽 팔꿈치를 90도만큼 회전시키라고 하는거죠. 그러면 둘의 포즈가 똑같죠?  바로 이 원리 입니다. 이해 되죠?

결국 3DS Max가 뱉어내는 데이터가 어쨌던간에 애니메이션에서 회전만 적용시킬 수 있는 방법만 있으면 되는건데... 유니티에서 이런 걸 공식적으로 지원해주는 찾으려고 포럼을 뒤졌지만 그런것 같진 않더군요. 그러면 파이프라인 어딘가에서 회전 이외의 키프레임 정보를 지워버리는 방법이 없을까 찾아봤지요. 불행히도 딱히 대답을 찾을 순 없었습니다. 심지어는 물어보는 사람조차 없더군요. (이 글 쓰고 나서 이 질문을 한 사람을 찾았으나 마땅한 답변이 없더군요. 지금 달린 답변은 제가 이 글 쓰고 난 뒤 단 것 -_-.)

뭐든간에 좋은소식은...... 제가 그 꼼수를 찾아냈다는 거지요. 무핫핫..... :D

이렇게 했답니다. (코드는 조 밑에~)
  1. Asset/Editor 폴더 안에 ConvertToRotationOnlyAnim.cs 라는 스크립트 파일을 만듭니다.
  2. 이 스크립트를 호출하는 메뉴 항목을 만듭니다.
  3. Unity에 애니메이션을 import 해옵니다. (Unity가 애니메이션 파일이라고 생각하는 한 어디서 이 애니메이션을 만들었는지는 상관없습니다.)
  4. import해온 애니메이션 애셋에 마우스 오른쪽 버튼을 누르고, 단계 #2에서 추가했던 메뉴 아이템을 선택합니다.
  5. 이제 스트립트 안에서 propertyName 필드 중에 "m_LocalRotation"이란 이름을 포함한 놈만 복사합니다.
  6. 이제 _rot 애니메이션 클립을 게임오브젝트의 애니메이션 컴포넌트에 대입합니다.
  7. 시작 버튼을 누르고..... 즐거워 해봅시다.. 얼씨구나.. 지화자~ -_-
그리고 이게 위에서 말씀드린 스크립트의 전체 소스코드. 주석을 잘 달아놨으니 영어공부할겸..... 대충 보고 이해하세요. ㄱ ㄱ ㅑ ~ -_-


using UnityEditor;
using UnityEngine;

using System.IO;

public class ConvertToRotationOnlyAnim
{
    [MenuItem("Assets/Convert To Rotation Animation")]
    static void ConvertToRotationAnimation()
    {
        // Get Selected Animation Clip
        AnimationClip sourceClip = Selection.activeObject as AnimationClip;
        if (sourceClip == null)
        {
            Debug.Log("Please select an animation clip");
            return;
        }

        // Rotation only anim clip will have "_rot" post fix at the end
        const string destPostfix = "_rot";

        string sourcePath = AssetDatabase.GetAssetPath(sourceClip);
        string destPath = Path.Combine(Path.GetDirectoryName(sourcePath), sourceClip.name) + destPostfix + ".anim";

        // first try to open existing clip to avoid losing reference to this animation from other meshes that are already using it.
        AnimationClip destClip = AssetDatabase.LoadAssetAtPath(destPath, typeof(AnimationClip)) as AnimationClip;
        if (destClip == null)
        {
            // existing clip not found.  Let's create a new one
            Debug.Log("creating a new rotation only animation at " + destPath);
            
            destClip = new AnimationClip();
            destClip.name = sourceClip.name + destPostfix;

            AssetDatabase.CreateAsset(destClip, destPath);
            AssetDatabase.Refresh();

            // and let's load it back, just to make sure it's created?
            destClip = AssetDatabase.LoadAssetAtPath(destPath, typeof(AnimationClip)) as AnimationClip;
        }

        if (destClip == null)
        {
            Debug.Log("cannot create/open the rotation only anim at " + destPath);
            return;
        }

        // clear all the existing curves from destination.
        destClip.ClearCurves();

        // Now copy only rotation curves
        AnimationClipCurveData[] curveDatas = AnimationUtility.GetAllCurves(sourceClip, true);
        foreach (AnimationClipCurveData curveData in curveDatas)
        {
            if (curveData.propertyName.Contains("m_LocalRotation"))
            {
                AnimationUtility.SetEditorCurve(
                    destClip,
                    curveData.path,
                    curveData.type,
                    curveData.propertyName,
                    curveData.curve
                );
            }
        }

        Debug.Log("Hooray! Coverting to rotation-only anim to " + destClip.name + " is done");
    }
}

2011년 5월 23일 월요일

NVidia 텍스처 툴에 컨벌루션 필터링을 추가하는 방법


몇 달 전부터 올릴려고 했던 글인데... 회사 법무팀의 허락을 받느라 좀 지체되었다. 드디어 올려도 된다는 허락을 받았으니.. 아싸 -_-;

배경
NVidia 텍스처 툴(NVTT)에 컨벌루션 영상처리 기법을 추가하게 된 계기는 회사에서 아티스트들의 요청 때문이었다. NVTT 1.0은 밉맵에 샤프닝(sharpening) 필터를 적용하는 옵션이 있었는데, NVTT 2.0에서는 삭제... (추후 추가할 예정이었으니 1년이 넘도록 추가되지 않음... -_-)  NVTT가 오픈소스 프로젝트가 된 이후로는 개발도 뜸해서 이 기능이 추가되길 기다리기 보다는 직접 추가하기로 맘을 먹었다.

어차피 샤프닝 필터는 2D 커널을 이용한 컨벌루션에 지나지 않으니 차라리 보다 범용적인 컨벌루션 필터를 추가해서 아무 계수나 받는 게 낫다고 생각했다. 이러면 샤프닝 뿐만이 아니라 컨벌루션을 이용하는 영상처리 기법은 모두 돌릴 수 있으니....

NVTT 소스 코드 수정
자 그럼 본격적으로.. 실제 코드를 들여다 볼 시간.. 사실 별로 추가할 코드가 많진 않다. 6개 파일에 코드 몇줄만 추가하면 되니까... 

Step 1. NVidia 텍스처 툴 프로젝트 웹페이지에서 revision 1277을 받는다.
최근 revision에서 이 코드를 테스트해보진 않았는데 별 차이는 없을거라 생각한다. NVTT 개발자가 소스코드 전체를 뒤집이 엎진 않은 이상... -_-;

Step 2. src/nvimage/Filter.h 파일을 열고 다음의 생성자를 추가한다.

Kernel2(uint width, const float * data);

Step 3. src/nvimage/Filter.cpp 파일 안에 다음의 함수를 추가한다.
Kernel2::Kernel2(uint ws, const float* data) : m_windowSize(ws)
{
    m_data = new float[m_windowSize * m_windowSize];

    memcpy(m_data, data, sizeof(float) * m_windowSize * m_windowSize);
}

Step 4. src/nvimage/FloatImage.h 파일 안에 다음 함수를 선언한다.
NVIMAGE_API void doConvolution(uint size, const float* data);

Step 5. src/nvimage/FloatImage.cpp 파일 안에 다음 함수를 구현한다.
void FloatImage::doConvolution(uint size, const float* data)
{
        Kernel2 k(size, data);
        AutoPtr tmpImage = clone();

        for(uint y = 0; y < height(); y++)
        {
            for(uint x = 0; x < width(); x++)
            {
            for (uint c = 0; c < 4; ++c )
            {
                pixel(x, y, c) = tmpImage->applyKernel(&k, x, y, c, WrapMode_Clamp);
            }
        }
    }
}

Step 6. src/nvtt/nvtt.h 파일 안에서 struct TexImage를 찾아 다음의 함수를 선언한다.
NVTT_API void doConvolution(unsigned int size, const float* data);

Step 7. src/nvtt/TexImage.cpp 파일 안에 다음 함수를 구현한다.
void TexImage::doConvolution(unsigned int size, const float* data)
{
    if (m->image == NULL) return;

    detach();

    m->image->doConvolution(size, data);
}

사용법
새로 추가된 컨벌루션 필터를 사용하는 법은 매우 간단하다. 이미 image 라는 TexImage 개체가 있다면 간다히 이렇게 사용한다.

const int kernelSize = 3;    // 3 x 3 커널을 사용한다.

// 대충 내 맘대로 찾아낸 샤프닝 계수.
const float sharpenKernel [] = 
{
    -1/16.0f, -2/16.0f,     -1/16.0f,
    -2/16.0f, 1 + 12/16.0f, -2/16.0f,
    -1/16.0f, -2/16.0f,     -1/16.0f,
};

image.doConvolution(kernelSize, sharpenKernel);


끝! (정말 간단하지? -_-)


2010년 7월 7일 수요일

이것 저것: 비디오 게임 최적화

  • NVIDIA Texture tool 2.0.8 이 릴리즈되었습니다: 특별히 변한 것은 없습니다. 제가 정말 원햇던 feature는 텍스처 툴 1에서 지원했던 sharpen 필터인데요.. 2.2 버전에서야 지원할 예정이랍니다.. 대체 몇년이 걸릴지... 참고로 NVIDIA 텍스처 툴은 오프라인 DXT 압축에서 매우 쓸만한 놈입니다.
  • GPU Pro 가 출시되었습니다. 그 유명했던 ShaderX의 뒤를 잇는 책입니다. 이름이 좀... 거만해졌죠? ... -_-
  • 리얼타임 렌더링의 저자 Eric이 Video Game Optimization이란 책을 추천했군요. 비디오 게임 최적화를 다룬 책이 별로 없었던듯 한데 한번 읽어봐야겠습니다.
  • Python 2.7도 릴리즈 되었습니다.  C#의 점점 위세를 넓혀가고 있지만 여전히 파이썬 많이 쓰죠?