꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.

버그의 추억

QA 2013. 7. 18. 21:35
Posted by 비회원


게임 Test라는 걸 하다보면 참 많은 버그들을 만난다. 그중에는 이게 버그인가 싶기도 한 버그들도 있었고 버그가 아닌 걸로 판정난 것들도 있었다. 가끔씩은 테스터들끼리 버그다 아니다로 말싸움을 하는 일도 있다. -_-;


피파 온라인

담당 게임은 아니었지만 축구 게임을 좋아해서 당시 회사 사람들과 많이 플레이 했었는데 사람들에게 얻은 칭호가 "사파 축구" 였다.

왜 그런가하면 좀 정신나간 플레이로 언제나 골을 넣었기 때문... 이것저것 방법이 있었는데 그중에서도 가장 쉽고 강력한 기술이 뭐였냐면 시작하자마자 공을 하프라인 뒤로 뺀 뒤 대쉬 후 뻥슛을 하면 상대방 골키퍼 AI가 골라인 근처에 있다가 공이 골키퍼 뒤쪽으로 떨어져 골을 넣는 기술이었다.

tumblr_maj0iqzb0q1r2knh6
당하는 사람은 당연히 시작하자마자 한 골 먹히고 시작하기 때문에 멘붕..


그런데 이게 될려면 조건이 있다. 중거리 슛으로 유명한 선수들만 가능하다는 것. (제라드라던가) 아닌 애들은 그냥 골키퍼 앞에서 공 떨어진다. 그리고 무조건 가운데 라인에서 슛을 해야 된다는 것.


좀 진지를 빨고 얘기하자면 게임과 테스트를 데이터로서 접근해야 된다고 생각하게 된 계기였다.상황에 따른 변수가 많은 축구 게임도 이렇듯 정확한 데이터와 상황을 정의하면 버그를 찾아 낼수 있는데 당연하지만 어떤게임이든 정확한 데이터가 있으면 재발생 스텝을 좀 더 쉽게 알아 낼 수 있다. 하지만 아쉽게도 우리나라 게임 명세서에서 이런 부분을 챙기기에는 많은 방해물이 있다.

아참.. 내가 쓰기 시작하고 얼마 안되서 결국 그 기술은 패치됐다.


XL1 레이싱

CBT 준비 테스트 중 최고속도를 낼 수 있는 차로 멈춰 있는 차를 받아버리면 박힌 차가 하늘의 별이 되는 버그가 있었다.

딱 요런 느낌


QA 기간 후반부에 발견된 버그였는데 꽤 간단한 버그였는데도 나중에 발견된 이유는 레이싱 장르에 대한 테스트가 처음인 것도 그렇고 일반적인 상식으로는 부딪치지 않고 서거나 피하는게 당연하기 때문에 유즈케이스로서 포함할 생각을 못했기 때문이었다. 게임 장르가 천차만별이다보니 장르에 맞는 유즈케이스를 만드는 것도 중요하고 그 안에 포함될 시나리오도 상식/비상식을 모두 포함해야 한다는 걸 경험으로 알게된 순간이었다.


"에이 설마 유저들이 그러겠어요?" 유저들은 그런다.


더 적어 올릴만한 버그들이 좀 더 있지만 아직 서비스하고 있는 게임들도 있고하니.. 우선은 위 2가지 추억만 남겨본다.

댓글을 달아 주세요

  1. 알 수 없는 사용자 2013.07.25 07:00  댓글주소  수정/삭제  댓글쓰기

    없는 버그도 찾아내는 유저들의 놀라운 능력을 보면 엄청나지요
    절대로 안할 거 라고 생각하면 꼭 합니다.

Posted by 비회원

C++에서도 coroutine & yield 를 쓴지 벌써 1년이 지났습니다. 그리고 지난 1년간 C++에도 많은 일이 있었습니다.

그 중 제가 주목한 한가지는 바로 C++ 표준에 드디어 coroutine이 들어갈 수 있는 가능성이 열렸다는 것입니다!

바로 boost 1.53 버전에 coroutine이 정식으로 포함된 것인데요.

오늘은 이 boost 1.53 버전에 포함된 coroutine에 대한 소개를 해보려고 합니다.


소개를 시작하기에 앞서 한가지 알려드리자면..

이 글을 쓰고 있는 2013년 7월 16일 현재 boost는 1.54버전까지 릴리즈 되었습니다.

하지만 불행하게도 boost 1.54버전에는 coroutine에 문제가 있어 coroutine을 사용할 수 없습니다.

(http://boost.2283326.n4.nabble.com/boost-1-54-needs-a-bugfix-td4649209.html 참조)


boost에 정식으로 포함된 coroutine은 비공식 coroutine과는 인터페이스가 약간 다릅니다.

보다 더 명확하고 더 coroutine의 개념을 충실하게 구현하고 있습니다.


간단하게 사용법에 대해서 살펴보도록 하겠습니다.

먼저 아주 단순하게 coroutine 객체를 생성해서 실행하는 과정입니다.

위 코드의 결과는 아래처럼 나오게 됩니다.

coroutine의 실행은 처음 접한 분들에게는 굉장히 생소할 수 있는 동작을 보입니다.

caller()를 호출할때마다 coroutine을 실행한 위치로 빠져나오고 다시 coro() 형태로 coroutine을 실행하면 마지막으로 caller()를 호출한 다음 명령부터 실행되는것을 확인할 수 있습니다.


공식 coroutine은 비공식 coroutine과 다른점이 몇 가지 있는데요.

첫번째는 비공식 coroutine의 경우 생성한 이후에 coro(); 처럼 한번 실행해주어야 switch가 일어나지만 공식 coroutine의 경우 생성하면 바로 switch가 일어나고 바로 TestFunction이 실행됩니다. coroutine 함수가 생성과 동시에 바로 실행이 되면서 밑에서 설명할 coroutine의 반환값에 대해 더 제대로 된 구현이 가능해졌다고 생각됩니다.


두번째는 인터페이스입니다. 비공식 coroutine에서는 coroutine 함수 안에서 self.yield(); 처럼 self 객체의 함수로 호출해주었는데 공식 coroutine에서는 caller(); 와 같은 형태로 실행됩니다. 만약 이름을 caller가 아닌 yield로 한다면 다른 언어에서의 coroutine의 문법과 거의 동일하게 사용이 가능해집니다.


반환값이 있는 coroutine을 사용하는 경우에는 다음과 같이 할 수 있습니다.

역시 이 코드의 결과는 다음과 같습니다.

caller( 반환값 ) 을 호출해주면 coroutine을 호출한 곳에서 get() 를 이용하여 반환값을 받을 수 있습니다. 그리고 coroutine을 실행해주는 operator()는 coroutine객체의 참조를 반환하기 때문에 coro().get() 의 형태로 coroutine을 실행함과 동시에 반환값을 받는 인터페이스 사용이 가능합니다.


주의할 점은 반환값을 주기 위해 coroutine의 마지막에 caller()를 이용하여 반환값을 넘긴 부분인데요. 이렇게 되면 coroutine은 더이상 실행할 명령은 없지만 완전히 끝나지 않은 상태가 됩니다. 끝나기 직전에 switch가  일어난 것이죠. 그래서 마지막에 다시 coroutine을 실행해서 완전히 종료되도록 하기 위해 if( !coro ) 대신에 if( !coro() )를 사용한 것입니다. 물론 coroutine은 완전히 끝나지 않은 상태에서 객체가 삭제되도 문제는 없습니다. coroutine에서 생성한 스택 데이터들도 모두 coroutine이 삭제될때 같이 해제가 되는것을 보장합니다.


coroutine이 실행될 때 인자값을 넣어줄 수도 있습니다.

역시 마찬가지로 다음과 같은 결과를 확인할 수 있습니다.

coroutine을 실행할때마다 coroutine에 새로운 인자값을 넣어줄 수 있고 그 인자값을 받아서 사용할 수 있습니다. 참고로 이러한 동작은 비공식 coroutine에서는 지원하지 않는 내용입니다.


인자값 전달은 여러개(현재 최대 10개)도 가능하지만 이 경우 인터페이스는 약간 달라집니다.

이 코드의 결과는 다음과 같습니다.

여러 인자값의 지원은 boost tuple을 사용하여 구현되어 있습니다.


추가로 많은 분들이 관심가질만한 부분이 바로 coroutine의 성능일 것이라고 생각되는데요

boost coroutine의 performance 페이지에서 대강의 정보를 확인할 수 있습니다.


그래도 조금 더 와닿는 테스트를 원하시는 분들을 위해서 간단하게(그리고 급하게) 테스트 코드를 작성하여 측정해보았습니다.


먼저 1024바이트의 데이터를 coroutine의 stack에 할당하고 switch를 1000000번 일으키는 것을 100번 반복하여 시간을 측정하도록 한 코드입니다.


그리고 그냥 1000000번 for문을 돌면서 위 테스트 코드와 비슷한 로직 처리를 하는 코드입니다.


이것을 Intel i5 CPU에서 10번 반복하여 측정한 결과

coroutine을 사용한 결과는 평균 0.0819799 가 나왔고 그냥 for문을 돌린 결과는 평균 0.0195949 가 나왔습니다. 단순 계산으로 switch 비용은 0.062385 정도라고 생각할 수 있겠네요.. 

(참고로 win32 API인 Fiber를 사용하는 비공식 boost coroutine 으로 같은 로직을 실행했을 때 평균 0.17250125 이 나왔습니다)


여러분들은 어떻게 생각하실지 모르겠으나 제가 내린 결론은 boost coroutine은 꽤 쓸만한 물건이라고 생각됩니다.

댓글을 달아 주세요

  1. Favicon of http://kernys.net kernys 2013.07.17 17:49  댓글주소  수정/삭제  댓글쓰기

    우왕 겁내 좋다!!! 감사합니다!

  2. woo 2013.07.23 10:20  댓글주소  수정/삭제  댓글쓰기

    와우.. 점점 막강해 지네요

  3. Favicon of https://gamedevforever.com Junios 2013.07.30 10:33 신고  댓글주소  수정/삭제  댓글쓰기

    무서운 이야기가 있군요. 잘 보고 갑니다. ㅎ

Posted by 친절한티스

디버깅을 하다보면 참 난감한 상황에 빠질때가 많습니다. 프로그램이 실행 도중 크래시가 났을 경우 덤프와 심볼 파일이 있다면, 크래시가 발생한 위치와 원인을 손쉽게 찾아낼 수가 있지만, 그렇지 않다면 막막할 뿐입니다. 하지만 절망 하기에는 이릅니다. 크래시가 발생한 주소라도 있다면, 아직 희망의 끈이 있습니다. MAP 파일을 이용하면 크래시가 발생한 위치를 대략적으로 찾아낼 수 있기 때문이죠.


프로젝트 옵션에서 MAP 파일 관련 옵션은 기본적으로 비활성화 되어있습니다. 먼저 이 옵션을 아래와 같이 활성화 해주어야 MAP 파일이 생성됩니다.



이와 같이 설정후 빌드하면 MAP 파일이 생성되는데 심볼 파일과 함께 항상 잘 보관해두도록 합시다.


이제 이를 가지고 크래시 위치를 찾아내는 방법을 살펴보겠습니다. 먼저 크래시를 내야겠죠? 아래와 같이 간단하게 크래시를 유발시켜보겠습니다.



이런 메모리 해제를 두번씩이나 해제 해주고 있군요. 이 코드를 실행하면 아래와 같이 크래시가 펑~ 하고 납니다.



오류 내용을 잘 보시면 크래시 주소가 나와있습니다. 주소라도 남은게 다행입니다. 이제 MAP 파일을 통해 이 주소를 찾는 것입니다. 먼저 MAP 파일을 열어보겠습니다. MAP 파일은 일반 메모장등으로도 열어도 되지만, 좋은 Viewer 들도 많으니 그 것들을 이용해도 좋습니다. 여기서는 그냥 메모장으로 열겠습니다.


MAP 파일을 열어보면 조금 알아보기 힘든 내용들이 주르르륵~ 나오는데 겁먹을 필요 없습니다. 중요한 부분은 Publics by Value에 표시되는 함수명과 Rva+Base 에 표시되는 주소, 그리고 Lib:Object에 표시되는 모듈 이름 입니다.


위에서 크래시가 난 주소를 가지고, Rav+Base에서 크래시 주소의 바로 윗 주소를 찾습니다. 여기서는 00411f50 이군요. Public by Value의 함수명을 살펴보겠습니다. 조금 이상한 특수 문자들이 섞여있지만 ( ?? 같은 문자들은 C++ 데코레이션 문자들입니다 ) CBar 클래스의 SetFloat 함수라는 것을 파악할 수 있습니다.



별로 어렵지 않습니다. 덤프 파일과 심볼 파일 없이 크래시 주소만으로 문제가 되는 함수를 찾아낼 수 있었습니다(정확한 위치까지는 아니지만...).


참조 : Debugging Applications for Microsoft .net and Microsoft Windows

댓글을 달아 주세요

  1. Favicon of http://bluekms21.blog.me 크로스 2013.07.12 14:12  댓글주소  수정/삭제  댓글쓰기

    굿팁 감사합니다.
    저 map옵션은 기본으로 꺼져있다고 하셨는데 활성화시킬경우 뭔가 성능저하라던가 문제는 없을까요?

  2. Favicon of http://bluekms21.blog.me 크로스 2013.07.12 14:12  댓글주소  수정/삭제  댓글쓰기

    굿팁 감사합니다.
    저 map옵션은 기본으로 꺼져있다고 하셨는데 활성화시킬경우 뭔가 성능저하라던가 문제는 없을까요?

    • Favicon of https://gamedevforever.com 친절한티스 2013.07.12 16:21 신고  댓글주소  수정/삭제

      네 이건 그냥 빌드타임때 심볼 만드는것처럼 맵 정보를 만드는거기 때문에 성능에 영향을 미치지는 않습니다. 다만, 빌드 시간 쪼끔 늘어나려나요? ㅎㅎ

  3. hovamoon 2013.07.14 09:10  댓글주소  수정/삭제  댓글쓰기


    오오~~감사합니다~~~~!

  4. azazel 2013.08.27 18:13  댓글주소  수정/삭제  댓글쓰기

    잘 읽고 배워갑니다. 감사합니다.

Posted by 김포프

벌써 5년넘게 써오고 있는 방법인데 생각보다 모르시는 분들이 많은 것 같아 이방법을 공유합니다.


밉맵?


일단 밉맵이 뭔지는 다 아시리라 생각하고 굳이 밉맵에 대해서는 설명하지 않겠습니다.  모르시는 분들은 여길 참고하세요.


문제점 및 일반적인 해결책


밉맵을 사용하다보면 정말 카메라를 가까이 들이밀지 않는한 텍스처의 디테일이 흐릿해 보이는 경우가 흔히 발생합니다.  보통 다음과 같은 방법들로 해결합니다.


흔히 쓰는 해결법 1 - 텍스처 크기 늘리기


밉레벨이 낮아질수록 텍스처 크기가 절반씩 줄어드는 거니 젤 높은 디테일의 밉멥 텍스처크기를 크게 키워주면 그만큼 흐려지는 현상이 덜합니다. 하지만 메모리를 많이 잡아먹는다는 단점이 있어서 정 필요한 때만 제한적으로 사용하곤 합니다. (왜 흔히 라고 한거지 그럼 -_-)


흔히 쓰는 해결법 2 - 밉맵 바이어스 쓰기


이걸 고치기 위해 흔히 쓰는 해결법은 밉맵 bias를 조절하는 방법입니다. 샘플러스테이트에서 정해주는 방법도 있고 셰이더에서 해주는 법도 있습니다. 뭐든 나쁜 방법은 아니고 가장 널리 쓰는 방법인데 여러가지 단점이 있습니다



  1. 고디테일의 텍스처(크기가 큼)를 더 많이 쓰도록 bias를 주므로 텍스처 캐쉬의 성능저하 (그만큼 넣어야할 데이터가 많으니)

  2. 밉맵이 원래 해결하려고 하는 거리에 따른 애일리어싱 문제가 쉽게 더 생긴다.

  3. 모든 경우에 적당히 잘 동작하는 bias 값을 찾기가 쉽지 않다.


흔히 쓰는 해결법 3 - 밉맵 생성시 사용하는 필터 다르게 사용하기


보통 bilinear 필터를 사용해서 밉맵을 만드는게 일반적입니다. 그럼 그냥 주변에 있는 이웃 픽셀들 2x2개 모아다가 균등하게 혼합하는게 전부입니다. 이 외에 kaiser 필터 등을 사용하면 좀더 낫다고 해서 그렇게 하는 사람들을 봤지만... 개인적으로는 별 효과가 없다고 생각합니다.



제 해결법 - sharpening filter


생각보다 매우 간단합니다. 그냥 밉맵 텍스처에 sharpening 필터 한번 더 먹여주면 됩니다. -_- 사실 밉맵들이 흐릿해 보이는 이유가 bilinear 필터링만 쓰면 그냥 경계를 뭉게버린다는건데 여기다 sharpening 필터 한번 먹여주면 경계부분은 다시 적당히 또렷하게 살아나거든요... 


오프라인 프로세스라 게임성능에 지장도 없고... 흔히 쓰는 해결법 2에서 말씀드렸던 단점들도 없습니다.. 그냥 밉맵만드실때 이런순서로 만들어 주시면 됩니다.


  1. 밉맵 0으로부터 밉맵 1 생성 (bilinear filter)

  2. 밉맵 1에 sharpening filter 적용

  3. 2번 결과물로부터 밉맵 2 생성(bilinear filter)

  4. 밉맵 2에 sharpening filter 적용

  5. 밉맵 끝까지 만들때까지 반복...


이 방법을 대충 포토샵으로 흉내낸걸 보여드리면 대충 이렇습니다. 오른쪽이 제 방법입니다.






꽃미남 포프였습니다.







댓글을 달아 주세요

  1. t 2013.06.13 12:20  댓글주소  수정/삭제  댓글쓰기

    저희 회사의 아트분께 보여드렸더니 포토샵에서 nVidia의 dds 플러그인으로 밉맵 필터 설정을 해서 저렇게 쓰고 있다고 하네요. 이미 대부분이 저렇게 쓰고 있으니 특별히 언급하지 않는 것인지..

    • Favicon of https://gamedevforever.com 김포프 2013.06.13 15:28 신고  댓글주소  수정/삭제

      아 거기서는 아티스트가 직접 밉맵까지 만들어서 저장하나 보네요? 제가 아는 회사들은 대부분 아티스트가 첫번째 밉레벨만 만들고 나머지 밉체인은 파이프라인에서 구워주곤 했어요.

  2. Favicon of https://gamedevforever.com 대마왕J 2013.06.14 15:18 신고  댓글주소  수정/삭제  댓글쓰기

    저희 회사 같은 경우는... 포토샵의 dds 플러그인에서 밉맵 필터 설정도 저렇게 하는 것과 함께, 밉맵의 단계를 줄여서 용량 절약과 바이어스 효과를 동시에 목적하기도 했습니다.
    추가로 배경 타일 이미지 같은 경우는 밉맵 때문에 타일링이 깨지는 경우도 발견되어서, 아예 밉맵을 다 뽑아서 밉맵들의 타일링과 샤프닝을 수작업으로 다 만져주고 저장하기도 하였습니다.
    하지만 이젠 유니티를 쓰니 안될거야...

  3. 베지타 2013.07.26 10:23  댓글주소  수정/삭제  댓글쓰기

    ㅎㅎ 좋은팁 감사합니다.
    저희팀 디자이너분들하고 내용 공유했는데 다덜 좋아하시네요
    기존 텍스쳐를 다시 손봐야하는 최소한의 비용만 지불하구
    퀄리티 향상이 한번 더 확 되겠네요ㅎ

Posted by ozlael


모바일 게임을 제작하다보면 메모리 혹은 패키지 용량 문제로 이미지 파일의 크기를 최소화 시키는 것이 중요하지요. 특히 2D 게임의 경우 더더욱 중요하지요. 그러한 관점으로 이미지를 줄이는 데 있어 많은 방법이 동원되곤 하지요. 버튼같은 경우 9분할 이미지를 사용한다거나, 이펙트같은 경우는 half-size로 줄이는 등등의 여러 방법이 동원되곤 하지요.

출처 : http://renderhjs.net/shoebox/slice9.htm


이러한 이미지 면적 자체를 줄이는 것도 중요하지만, 이미지 정밀도를 낮추는 것도 중요하지요. 픽셀당 4바이트를 차지하던것을 정밀도를 줄여서 2바이트로만 바꿔줘도 크기는 반으로 확 줄어들게 되는 것이지요. 하지만 그 대신 정밀도가 줄어드는 만큼 색의 경계가 두드러지게 티나게 되는 부작용이 발생하게 되지요. Mach Band 또는 Banding Artifacts등으로 불리기도 하는데 이는 디더링 과정을 통해서 극복을 가능케 하지요.

출처 : http://www.codeandweb.com/texturepacker/features


이러한 정밀도를 낮추고 디더링을 해주는 과정은 2D 게임 제작에 있어 매우 중요한 영역을 차지하기 때문에 기능을 지원해주는 툴들이 존재하지요. 그 중 대표적인게 TexturePacker라는 툴이지요.  이 툴은 이미지들을 하나의 이미지 시트로 묶어주어 렌더링 배치를 줄일수 있는 기반을 마련해주기도 하지요. ( 사실 그게 주 기능이지요) 때문에 유니티로 개발을 하던 코코스로 개발을 하던 코로나로 개발을 하던 상관없이 이러한 툴들은 필수적으로 사용이 되어야 하지요. 스프라이트 시트에 대한 설명은 제가 주저리주저리 하는 것 보다는 두 영상이 더 이해하시기 쉬울 것 같네요. ( 뭐 사실 다들 아시는 내용이니 걍 대충 넘어가려구요 ㅋ)


하지만 모든 이미지를 시트로 묶어야만 하는 것은 아니죠. 오히려 시트로 여러 이미지를 묶는 것이 독이 되는 경우도 있지요. 예를 들자면 상점 혹은 인벤토리에서의 아이템 이미지가 있을거예요. 아이템의 이미지들을 하나로 묶은 시트가 있다고 가정해볼까요? 

근데 정작 이 시트에서 필요한것은 고작 칼 한자루 뿐인 상황이예요. 

게다가 방패, 갑옷, 투구 등등이 각각 서로 다른 스프라이트 시트에 있다면 재수없으면 필요한 정작 이미지의 백배가 넘는 메모리가 낭비 될 수도 있을거예요. 그래서 이러한 경우는 그냥 각각 이미지가 별개의 파일로 존재하는게 나을 수도 있을거예요. ( pow2문제나 서칭 비용 고민은 잠시 내려놓아요 <-무책임ㅋ)

근데, 이때 문제가 발생하네요. 수십여장의 이미지들을 정밀도를 낮추고 디더링을 적용할라니 막막하네요. 포토샵에서 일일이 저장할떄 신경써서할라니 빡세요. 어랏? 알씨가 일괄적으로 이미지 포맷을 변경해주는 기능이 있네요. 컥! 근데 알파채널이 날라갈 뿐더러 디더링 따위는 해주지도 않아요. TexturePacker에서 처리가 가능하지만 이태리 장인마냥 파일을 한땀한땀 찍을라니 성질부터 뻗치네요.


그렇다면 한방에 이미지들의 정밀도를 낮추고 디더링 할 수 있는 방법은 없는것일까요? 왜 없겠어요 있으니 이렇게 제가 글을 싸지르는 것이겠지요. (사실 앞선 내용들은 이를 설명하기 위한 밑밥인걸요 ㅋ) 바로 그 마법이 가능한 ImageMagic 이란 툴을 소개하고자 해요. 사실 아까까지만 해도 이런 고민으로 찾아보다가 지인분의 소개로 마법을 접하고나서 기쁨을 감추지 못하고 똥글을 싸제끼고있네요 ㅎ 요놈 참 물건이더군요. 이미지 포맷 변환 외에도 많은 기능이 존재하는데 커맨드 라인 기반이라 활용도도 매우 높겠더군요. 게다가 오픈소스(라 쓰고 무료라 읽는다)네요. 거두절미하고 일단 받아서 설치 ㄱㄱ 하세요

http://www.imagemagick.org/script/binary-releases.php#windows

그리고 명령 프롬프트 창을 열어서 이미지 파일이 있는 디렉터리로 이동한뒤 다음과 같이 치면 포맷을 변환해요.

convert -dither FloydSteinberg -colors 64 원본.png 대상.png

딱 보시면 옵션중 dither는 디더링 알고리즘을 선택하는 옵션인 것은 알겠어요.(사실 없어도 기본적으로 활성화 되요) 근데 어랏 비트뎁스를 지정하는 것이 아니라 colors란 옵션을 지정하네요. 물론 depth라는 옵션으로 설정 할 수도 있어요. 하지만 colors란 옵션을 통하면 더 많은 용량을 절약할 수 있더라구요. 이 옵션은 말 그대로 몇개의 컬러를 사용 할 것인지를 나타내는 것이예요. 

출처 및 참고 : http://www.imagemagick.org/Usage/quantize/#colors

PNG는 팔레트 기반 데이터를 제공하기때문에 총 색 수를 줄여서 용량을 줄일 수 있는 게 가능한가봐요. (이게 OpenGL ES에 올라갈때도 유효한가까지는 잘 모르겠네요;; 누가 알려주세요 ㅠ) 그리고 그 그 제한된 수의 색을 지정하는 데 있어 일정한 간격의 색차를 선택하는 것이 아니라 가변공간분할(Adaptive Spatial Subdivision) 알고리즘을 이용해서 선택한다고 하네요. 가변 공간 분할이 3D 렌더링할 때 컬링 공간 분할때나 봤었지 이미지 변환에서도 쓰이는 줄을 몰랐네요. 싱기방기 ㅋ 정확히 뭐 어떻게 되는건지는 잘 모르겠는데 아래 이미지를 보면 대충 느낌은 오는 것 같네요. (맞나? 맞겠죠?) colors 옵션 뿐만 아니라 다양한 옵션들이 있어서 마음만 먹으면 입맞에 맞게 세세하게 컨트롤이 가능 한 것 같아요.

출처 : http://www.flickr.com/photos/quasimondo/4251382278/sizes/m/in/photostream/


암튼 그래서 커맨드 라인을 통해서 이미지를 컨버팅 할 수 있는데 귀챦은게 하나 있어요. 소스 이미지를 "*.png"로 설정이 가능하지만 이렇게 되면 원본에 덮어 쓰는게 아니라 0.png, 1.png 등 그냥 순차적인 넘버링으로 나와버리는군요. 배치 스크립트로 디렉터리 내 파일 순회하면서 수행토록 해야겠군요. 다음과 같은 내용의 bat 파일을 만들어서 사용을 하면 한시름 덜게되었네요.

for %%f in (*.png) do convert -dither FloydSteinberg -colors 64 %%f %%f


아 이제 끝났네요. 디렉터리내 모든 이미지를 변환하는 저 한문장을 설명하기 위해 주저리 주저리 밑밥이 많았네요. 걍 처음부터 저것만 써놓으면 될것을 말이예요. 그러게 쓰다보니 왜그랬을까 싶네요 ㅋ 간만에 쓰는 글이라 손가락이 간질 간질 했나봐요. 늙으면 말이 많다더니 점점 타이핑이 늘어나는군요. 얼마전 꿈돌이가 성년이 되었다는 소식을 듣고 충격먹어서 제정신이 아닌가봐요. 그냥 나이먹더니 깨랑까랑하나부다 하고 이해해주세요 ㅎ 그럼 모두들 즐개발~

댓글을 달아 주세요

  1. Favicon of https://gamedevforever.com 대마왕J 2013.06.05 10:31 신고  댓글주소  수정/삭제  댓글쓰기

    유니티 같은 경우는... PNG로 만들어도 자기가 알아서 포맷을 다시 변경해 버리니 쓸모가 없어 보이네요.

  2. Favicon of https://www.facebook.com/jaesung.yi.5 하늘향기 2013.06.05 12:05  댓글주소  수정/삭제  댓글쓰기

    페북에서 본 그림마술이군요.
    저는 cocos2dx로 제작중인데 2.1.2부터 webp를 지원해서 몇몇이미지를 webp도 사용하고있는데
    이것도 괜찮아보이네요.
    오늘 한번 써봐야겠네요.

Posted by 김포프

생각해보니 개인적으로 PIMPL 패턴을 직접 구현해본적이 없다. 이런 패턴이 있다는걸 예전에 읽어본적은 있지만.. 그냥 '그닥 쓰고 싶은 생각이 안나는 놈?' 정도로 생각했다고 할까...


음 근데.. 지금 다루고 있는 코드베이스 안에는 PIMPL패턴이 아주 넘친다... 그래서 사용해본 뒤 소감이 어떻냐고? 안.좋.다. 게임 프로그래밍에서 이 패턴을 씀으로써 얻는 장점이 별로 없다고 생각한다. PIMPL 패턴의 장점이 뭘까? 다음 두가지 장점 정도가 아닐까?


  1. API 설계와 구현의 확연한 구분: 소프트웨어 아키텍트 마스터가 API를 멋지게 설계해두면 쫄개 프로그래머/코드 몽키들을 와라락 달려들어 잘 숨겨진 파일안에 구현을 함

  2. 헤더파일간의 의존성(dependency)가 적음 = 컴파일 시간이 빠름

일단 장점 1번이 게임 프로그래밍 업계에서 큰 의미가 있다고 생각하지 않는다. 어느 엔터테인먼트 업계에서도 그러듯이 게임의 요구사항은 끊임없이 변하며 그에따라 API도 수도없이 바뀐다. 즉, 마스터가 API를 멋지게 설계하는 일 자체가 별로 없단 이야기. 그리고 다른 프로그래머들로부터 구현파일을 숨긴다는거 자체도... 좀 웃기지 않나? 게임 프로그래머들은 전체 소스코드를 보는 걸 좋아한다. 라이브러리를 만들어서 파는 3rd party업체가 아닌이상... 정말 별 의미가 없음...


게임 프로그래머들이 PIMPL 패턴을 왜 쓰는 주 이유는 사실 2번이라고 생각한다. C++ 의 컴파일 속도는 매우 구려요 -_-;;; 하지만 PIMPL 패턴 보다 컴파일 속도를 향상시킬 수 있는 다른 방법이 존재한다. 물론 조홀라 비싼 incredibuild를 말하는 건 아니다. 여태까지 써본 방법중에 가장 빠른 건 이미 10년전부터 널리 애용되고 있는 유니티 빌드였다.. 물론 공짜 -_-v... 이에 대한 제대로된 한국말 설명은 다음의 슬라이드를 참고...



따라서 PIMPL의 장점이 그닥 중요하가 와닿지 않는 반면.. 단점은 아주 절절히 느껴지는게 문제....

  1. 코드 읽기가 더 귀찮아진다. 파일 여러개를 뛰어다녀야만 겨우 구현코드를 볼 수 있다는 단점....매조키스트 아닌 다음에야 이걸 좋아할리가...

  2. 듬성듬성한 메모리 할당: pimpl 개체를 만들기 위해 따로 new를 호출해줘야 한다. 메모리 파편화, cache 관리 등의 이유로 개인적으로 클래스의 모든 멤버가 한번에 메모리할당되는 걸 좋아하고, 그 외에도 어떤 개체의 크기를 쉽게 알아오기에도 한번에 할당되는게 좋다.

  3. 추가적인 포인터 참조 연산 = 아주 조금 더 느림.... 이정도 포인터 점프 한번 더 하는게 성능에 아주 큰 영향을 미치지는 않는다. 하지만 엔진쪽 프로그래밍 하는 사람으로써 이런 필요없는 포인터연산이 성능을 10프로 이상 떨어뜨리는 걸 수도없이 봤기에... 매우 까탈스러울수밖에 없다... 아마 실제로 이 포인터 참조에 걸리는건 CPU 사이클 4사이클 정도일 테지만... pimpl 개체의 메모리 위치가 떨어져 있을 수도 있으니 cache 관리까지 들어가면 더 느릴수 있다는 것.... 물론 메모리 할당 순서를 손수 잘 컨트롤 해주면 이런 문제를 피할수도 있겠지만... pimpl 패턴의 장점이 크게 없는 이상 왜 이런 쓸데없는 짓을 해야하나 생각까지 든다.

결론... 핌플 맘에 안듬.... -_- 아니면 내가 뭔가 놓치고 있는게 있나?



참고로 영어로 pimple은 뾰드락지를 말함 -_- 이런거...





댓글을 달아 주세요

  1. 제레미 2013.06.03 10:48  댓글주소  수정/삭제  댓글쓰기

    개인적으로는 Pimpl 패턴을 자주 사용하려고 하는 편이지만, 언급하진 단점에 대해서는 동의합니다.

    제가 생각하는 Pimpl 패턴의 장점 한가지만 추가하자면, "설계가 잘못된 방향으로 가는 것을 방지해준다" 입니다. 라이브 프로젝트이거나, 일정과 시간에 시달리다 보면 퍼블릭으로 멤버변수를 선언한다거나 getter setter 를 남발한다거나 영혼을 팔게 되는 경우가 종종있는데, 해당 클래스가 Pimpl 로 작성되어 있다면, 처음부터 땜빵코드를 넣지 않거나 나중에라도 수정하게 되었습니다.

    물론 처음부터 잘 하는 사람에게는 쓸모없는 장점입니다만..

    • Favicon of https://gamedevforever.com 김포프 2013.06.04 15:49 신고  댓글주소  수정/삭제

      영혼을 팔 일이 아예 없을정도로 잘하는 프로그래머가 존재하진 않을거 같은데요.... ㅎㅎ

      pimpl로 만들더라도 당장 급하면 pimpl에 땜빵함수를 만들고 pimpl을 포함하는 API에 떔빵함수를 만들어 그 함수를 그냥 호출해주는 경우도 많이 봤습니다 ㅎㅎ.

      사실 이건 pimpl의 문제가 아니라.. 이런 땜빵 함수를 만들경우 이걸 고치라는 task를 백로그에 추가해놓고 급한 불 끈뒤에 제대로 다시 고치는 자세/습관을 가지는게 더 좋지 않나 생각합니다.

    • 알 수 없는 사용자 2013.06.04 18:54  댓글주소  수정/삭제

      추가로 코드 리뷰가 정착된 팀에서도 마찬가지로 이런 문제는 많이 해소할 수 있다고 생각합니다. 저희 파트의 경우엔 리뷰어가 리뷰하지 않은 코드는 svn에 올리지 못하는 규정이 있는데 그래서 리뷰어가 좀 빡세긴 하지만 여러 문제를 빠르게 잡을 수 있는 효과가 있지요..

  2. Favicon of http://bluekms21.blog.me 크로스 2013.06.05 14:41  댓글주소  수정/삭제  댓글쓰기

    저는 코드 오픈도 사랑하지만 코드 캡슐화도 사랑합니다.
    대신 캡슐화를 할 클래스를 디자인하는 사람은 (최초 라이브러리 설계자) 그 클래스를 보증해야 하죠.

    pimple패턴은 그런점에서 좋기는 개뿔
    소스코드가 열려있는 경우 소스코드를 읽으려는 사람은 왠지모를 F12를 두세번 눌러 내부에서 구현을 봐야하고
    소스코드가 닫혀있는 경우 계속 new로 할당을 해줘야 해서 (별도의 메모리 할당자를 사용하지 않는 한) 메모리 단편화라던가 하는 문제가 생기겠습니다만
    구글 안드로이드 API들은 버전업할때마다 빌드조차 안되니까 좀 저런걸로 구현해줬으면 하기도 하고...
    뭔가 복잡한 심정입니다.. (먼산)

    ...그 대단하신 구글님이 자꾸 설계변경해서 API 구버전 호환 안되게 하는걸 보면
    pimpl 패턴은 전제부터가 불가능인건가.. 싶기도 하고요;;