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

그들은 끝장났는가?

기타 2012. 4. 23. 21:33
Posted by 알 수 없는 사용자

안녕하세요 코인입니다.

요즘 멘붕하고 삶에 자신감이 없어져서 글도 못쓰고 저번 연재도 그냥 건너뛰었는데 이래저래 죄책감에 몸부림 치다가 어떻게 한 번 써보기로 합니다. 죄송합니다 ㅠㅠ


이번에 꺼낼 이야기는 '일본 게임'에 대한 이야기입니다.


요즘 일본 게임 업계가 참 동네북처럼 여기저기 까이고 난리도 아닙니다. "your game just sucks" 라는 말도 면전에다가 하고.. 미야모토 시게루는 한국 왔더니 기자들에게 스마트폰 어찌 대처하느냐 질문을 회피하지 마라 이런 소리나 듣고..


어릴적에 일본 게임 열심히 하면서 자라온 분들도 계실테고, 그렇지 않은 분들도 계실겁니다. (하드코어하게 울티마나 주시자의 눈 발더스 게이트 등등을 하면서 커왔다거나...)


일단은 책을 하나 권할께요. 제가 책장수는 아니고 출판사나 번역가랑도 관계가 없으니까 오해는 하지 마시고 ㅠ



팩맨의 게임학이라는 책인데 들어보셨거나 벌써 보신 분들도 계실테지요.


팩맨하면 떠오르는게 뭐가 있으신가요? 쩔어주는 FSM이 떠오르는 개발자 분들 계십니까?

이 책은 100 페이지 남짓 하는 얇은 책입니다. 분량이 적다고 해서 돈이 아깝다는 생각을 하실지도 모르겠습니다만 단연코 책의 가치는 분량으로 결정되는게 아니라고 말씀드리고 싶습니다. 책 값에 비해 좀 얇긴 하군요. ;ㅁ; 저도 처음엔 너무 얇다고 생각했는데, 안에는 주옥같은 경험담과 조언, 그리고 일본 게임 업계 거물들의 대담으로 채워져 있습니다.


그러면 이번에 해볼 이야기인 '일본 게임 업계는 끝장났는가?' 에 대해서 이야기를 해보면요


저는 그렇게 생각하지 않습니다.


현재 일본 게임 업계는 '더 이상 자국 시장만으로 세계 시장까지 압도할 수 있는 상황이 아님' 이라는 점을 자각하지 못하거나, 자각은 하고 있는데 문제점을 해결하지 못하는 상황입니다. 간단히 말하면 옛날엔 우물 안 개구리가 일진이었는데 아직도 정신 못차리거나 혹은 우물 밖으로 나갈 수 없는 상황이라는 얘기지요. 아 뭔소리야.


그런 문제는 자각하고 만들어본 대작게임들은 하나같이 실패를 하고있고 (아수라의 분노 = 유저들의 분노) 작년 게임 업계에서 가장 수익이 좋았던 세가 마저도 콘솔 프로젝트나 스튜디오를 대폭 접거나 포기한다고 하니 정말 얼마나 심각한지 짐작도 안갑니다.


하지만 여전히 작은 시장 안에서 커다란 시도까지는 하지 못하지만 저 나름의 개성과 참신함을 갖추고 도전하는 작품도 적지 않습니다. 이런 게임들은 주로 PSP용으로 나오고 있고요.


그리고 서양 게임 역시도 조금만 삐끗하면 이런 함정에 빠질 수도 있다는 생각도 들고 있습니다. 모던 워페어 3를 보면 정말 그래요.


이런 게임들을 소개하는게 제가 글을 쓰는 목적인데요, 일단 게임 소개는 다음으로 미루면서 날로 먹도록 하겠습니다 ㅠ


한 줄 요약
- 일본 게임은 고전은 물론이고 현재도 보고 배울 요소가 많다고 생각함


반응형
,
Posted by 알 수 없는 사용자

이전의 내용은 다음의 링크에서 확인하시면 됩니다.

http://www.gamedevforever.com/141


  1. 빠르고 강력한 대칭키 방식의 암호

    대칭키 - 블록 암호는 구조가 단순하기 때문에 구현하기 쉽고, 비트열연산이 전부이므로 속도가 빠르다. 그에 비해 공개키 방식은 큰 수의 연산이 필수 이므로 큰 정수 연산을 수행하는 라이브러리가 필요하다. 결론적으로, 대칭키 방식의 암호는 비대칭키 - 공개키 방식의 암호에 비해 더 빠르다. 그 사실을 증명하기 위해 다음과 같은 두 코드를 작성 하였다. 두 코드 모두 OpenSSL에서 제공하는 crypto 라이브러리의 함수들을 사용하였다.

    AESSpeedTest.c

    #include <stdio.h>
    #include <stdlib.h>

    #include <openssl/evp.h>
    #include <openssl/aes.h>
    #include <openssl/err.h>

    int main(int argc, char *argv[])
    {
    uint8_t* plainText = (uint8_t*)malloc(sizeof(uint8_t) * 1024);
    uint8_t* cipherText = (uint8_t*)malloc(sizeof(uint8_t) * 1024 + EVP_MAX_BLOCK_LENGTH);
    uint8_t* key = (uint8_t*) "abcdefghijklmnop";
    unsigned int plainTextLen = 1024;

    EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX*)malloc(sizeof(EVP_CIPHER_CTX));
    int paddingLen = 0, cyperLen = 0; register unsigned i;

    ERR_load_crypto_strings();
    EVP_CIPHER_CTX_init(ctx);

    if(EVP_EncryptInit(ctx, EVP_aes_128_ecb(), key, NULL) != 1)
    {
    printf("ERR : EVP_Encrypt() - %s\n", ERR_error_string(ERR_get_error(), NULL));
    return -1;
    }

    for(i = 0; i < 1024 * 1024; i++)
    {
    EVP_EncryptUpdate(ctx, cipherText, &cyperLen, plainText, plainTextLen);
    }

    if (EVP_EncryptFinal(ctx, cipherText + cyperLen, &paddingLen) != 1)
    {
    printf("ERR: EVP_EncryptFinal() - %s\n", ERR_error_string (ERR_get_error(), NULL));
    return -1;
    }

    EVP_CIPHER_CTX_cleanup(ctx);
    ERR_free_strings();

    return EXIT_SUCCESS;
    }


    RSASpeedTest.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    #include <openssl/rsa.h>
    #include <openssl/evp.h>
    int main(int argc, char *argv[])
    {
    RSA *rsa = RSA_generate_key(2048, 3, NULL, NULL);;
    unsigned char* plainText = (unsigned char*) "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345";
    uint8_t* cipherText;

    register unsigned int i;

    if (RSA_check_key(rsa) != 1)
    {
    perror("Failed to generate RSA key");
    exit(0);
    }

    cipherText = (uint8_t *) malloc ((size_t) RSA_size(rsa));

    for (i = 0; i < 1024 * 1024 * (1024 / 245); i++)
    {
    RSA_public_encrypt(strlen((char*) plainText), plainText, cipherText, rsa, RSA_PKCS1_PADDING);
    }

    free(cipherText);
    RSA_free(rsa);

    return 0;
    }

    위의 같잖은 프로그램들[각주:1]을 이용해 1GByte에 해당되는 데이터를 각각 5번씩 암호화 하고 가장 느린 것 둘을 제외한 나머지의 평균을 비교해 보도록 하겠다. 여러번 반복해서 평균을 비교하는 이유는, 인코딩 하는 중간에 태스크 전환이 일어난다던가 하는 등의 프로그램 외적인 문제에 의한 로스 타임이 길어지거나 짧아질 수 있기 때문이다.

    그런데,공개키 방식의 암호화 작업은 너무 느리기 때문에 키의 크기보다 큰 데이터를 암호화해 주지 않는다. 그렇기 때문에 2048비트 크기의 키를 이용해서는 256 바이트 까지만 암호화가 가능하다. 그러나, 암/복호화 이외의 팩터 때문에 그 전부를 암/복호화 할 수 는 없다. 2048 비트 크기의 키를 사용하는 경우 암호화 할수 있는 데이터의 크기는 최대 245 바이트이다. 그래서, RSA 암호화 샘플에서는 반복횟수를 1024 * 1024 회가 아니라, 1024 * 1024 * (1024 / 245) 번을 반복한다.

    아래의 실행결과는 2.5GHz 인텔 i5 쿼드코어, 8G 메인메모리, Windows7 64비트 환경에서 실행한 결과이다. 잡다한 프로그램들이 상당히 많이 깔려있는 컴퓨터 인지라 다른 프로그램의 영향을 받았을 확률이 높다. 사실 쿼드코어 환경이라고 해도, 암호화과정 자체가 병렬처리 될 수 없기 때문에 코어 하나만 사용했다고 봐도 무방하다.

     real     0m3.869s
     user    0m3.837s
     sys     0m0.000s

     real     0m3.822s
     user    0m3.806s
     sys     0m0.015s

     real     0m3.838s
     user    0m3.790s
     sys     0m0.015s

     real     0m3.822s
     user    0m3.806s
     sys     0m0.015s

     real     0m3.822s
     user    0m3.806s
     sys     0m0.000s

    AESSpeedTest 실행결과

    AES의 평균 처리 속도는 약 3.801s이고, 초당 처리 바이트 수는 282,489,299 바이트이다. 128비트 AES를 사용하였기 때문에, 초당 17,655,581번의 AES 연산이 수행되는 셈이 된다.

     real     3m32.800s
     user    3m32.769s
     sys     0m0.000s

     real     3m32.613s
     user    3m32.566s
     sys     0m0.015s

     real     3m32.738s
     user    3m32.644s
     sys     0m0.015s

     real     3m32.831s
     user    3m32.722s
     sys     0m0.000s

     real     3m32.987s
     user    3m32.878s
     sys     0m0.046s

    RSASpeedTest 실행결과

    RSA의 평균 처리 속도는 212.644s 이고, 초당 처리 바이트 수는 5,049,481 바이트 이다. 2048비트 RSA를 사용하였으니, 초당 20,610 번의 RSA 연산을 수행한 셈이 된다.

    사실, 실험결과를 가지고 계산해 볼 필요도 없을 정도로 차이가 극명하다. 블록암호의 경우에는 16바이트 정도의 배열과 비트열을 가지고 깔짝대는 정도의 연산 뿐 이지만, 공개키 방식의 암호는 256바이트를 대상으로 덧셈, 곱셈, 정수 나눗셈을 해야 한다. 그러니 실험을 해볼 필요도 없이 AES의 속도 - 블록 암호의 속도가 빠를 수 밖에 없다.

    정녕, 단매에 죽고 싶은게냐? 대체 쓸데없는 실험은 왜한 것이냐!

    사실, 16바이트만 암호화 하는 경우는 있을 수 없는 데다가, ECB 방식은 쓰지 말라고 했으니, 위의 실험 결과는 사실 실제로 프로그램을 작성했을 때 어느정도의 시간을 필요로 하는지를 예측하는 데에는 사용할 수 없는 데이터 이다. 그래서, 여기에서 설명하고자 하는 것과는 직접적인 관계는 없지만, 실제 파일을 대상으로 암호화 작업을 했을 때 어느정도 시간이 소요되는지 간단한 실험을 해보도록 하겠다.

    1,761,397,888 바이트 짜리 파일을 다음의 커멘드를 이용하여 암호화 해 보니 다음과 같은 결과를 얻었다.

    time openssl aes-128-cbc -in 1761397888_byte_long.bin -K 000102030405060708090a0b0c0d0e0f -iv 000102030405060708090a0b0c0d0e0f -e -out encrypted_file.enc

    real 0m55.238s
    user 0m11.480s
    sys
    0m3.023s

    리얼이 상당히 길고, 유저 타임이 짧은 걸 봐서는 대부분의 시간을 파일 액세스 하기위해서 대기하는데 보냈다는 것을 알 수 있다. 어찌 되었든 우리에게 필요한 데이터는 두번째의 유저타임 뿐이다. 왜? 대부분의 경우 메모리내 데이터를 암호화 해서 보내지 덩치큰 파일을 암호화 해서 보내거나 하지는 않을 것 이기 때문이다. 대략 초당 153,431,871 바이트, 9,589,491개의 16바이트 블록을 암호화 할 수 있다는 것을 알 수 있다. 파일 액세스나 많은 다른 요소들 때문에 속도가 절반 이하로 줄어들기는 했지만, 실제 환경에서도 다른 작업과 병행해서 작업을 할 것 이므로 비슷한 정도로 암호화/복호화 할 수 있을 것 이다.

    AES의 경우, 속도를 희생한다면 대략 1KB ~ 4KB 사이에서 구현이 가능하다. 일반적인 방식을 사용여 효율을 우선시 한 경우 40KB 정도의 코드 및 데이터로 암복호화 모듈을 만들 수 있다. 그러므로, 효율이나 안전성을 우선으로 생각한다면, 공개키 방식 보다는 블록 암호를 사용하는 편이 더 나은 선택 이라는 것을 알 수 있을 것 이다.

  2. 둘을 적당히 섞은 PGP(Pretty Good Privacy) 방식의 암호체계

    앞에서 말 했듯이 비 대칭키 방식과 대칭키 방식은 각각의 특징을 가지고 있고, 서로 다른 상황에서 사용하기 위해서 만들어진 것이다. 그러나, 두 방식의 특징을 섞어서 장점만 취하고 싶은 것이 인지상정! 그래서 만들어진 것이 PGP 방식의 암호화 방식이다. PGP를 한마디로 설명하자면 '본문을 대칭키로 암호화 하고 그 대칭키를 공개키로 암호화 하여 전달하는 방식'이다.

    통신을 시도하게 되면 

    1. 통신을 위해 서버와 클라이언트가 각각 공개키/개인키를 생성한다.

    2. 상대방에게 생성된 공개키를 전송한다.

    3. 세션키로 사용할 대칭 키를 하나 생성한다. 이 세션 키는 말 그대로 통신을 수행하는 동안에 사용되는 대칭키 이다. 대칭키의 특성상 같은 키를 이용해서 많은 데이터를 암호화 하면 할 수록 깨기 쉬워지기 때문에, 말 그대로 한 세션 동안에만 사용되는 키는 상대적으로 공격에 강할 수 밖에 없다.

    4. 만들어진 세션키를 이용하여 전송 하고자 하는 데이터를 암호화 한다.

    5. 만들어진 세션키를 상대편의 공개키를 이용해 암호화 한다.

    6. 암호화된 세션키와 그 세션키로 암호화 된 데이터를 묶는다.

    7. 이렇게 만들어진 데이터를 전달해 주면 된다.!!!

    이렇게 묶어줌으로서 공개키 방식의 안전한 키 전달 방식과, 블록 암호 방식의 강력한 암호화 특성을 모두 취할 수 있는 아주 좋은 방식이다. 사실, 제목에는 어정쩡 하다는 자극적인 표현을 달았지만 실제로 기존에 나와있는 암호화 통신 방식중에 이 이상으로 좋은 방식이 있기 힘든 아주 좋은 방식이다. PGP는 어찌 되었든 '상용'이며, GNU에서 GNU Privacy Guard (GnuPG 혹은 GPG)를 PGP 5.x 를 대체하는 공개 소프트웨어로 제공하고 있다.

게임의 패킷을 전달 하기 위해 그냥 PGP나 GPG를 사용하는 것도 나쁘지 않은 선택이 되겠지만, 다목적의 통신 패킷을 사용하는 것은 효율성 면에서 그다지 좋은 선택이 아닐 수 도 있다. 그렇기 때문에 작업중인 게임의 특성에 맞추어 암호화 통신을 위한 프로토콜을 만들어 보는 것도 좋은 것 이다. 다음 번 부터는 프로토콜을 디자인 하는 작업을 해 보겠다. 프로토콜을 디자인 하는데 필요한 바탕 지식과, 프로토콜을 공격하는 공격 기법을 설명하고, 그 공격을 어떻게 회피할 지에 대해 설명을 하고 함께 프로토콜을 디자인 해 보도록 하겠다.

*     *     *

역시 생각만큼 생각을 풀어내는 것이 쉽지는 않네요, 어디 까지 상세하게 써야 하는 건지도 확실하지가 않은지라 할수록 어렵네요. 계속 써야 하는지도 막막 고민이 되네요... 뭐, 보든 말든 어찌 되었든 쓸겁니다.

  1. 이전의 포스팅에서 하지말라고 했던 모든 짓을 했다고 해서 뭐라 하지는 말아 주세욥! 말 그대로 실험을 위해서 작성한 코드이고, 모든 사람들이 쉽게 알아보게 하도록 하기 위해 그렇게 했습니다. [본문으로]
반응형
,
Posted by ozlael


들어가며

요즘에는 스마트폰을 사용하지 않는 사람을 찾아보기가 힘들지요. 저 역시도 한시도 스마트폰을 품에서 떠나보내지 않고 생활하고 있습니다. 정말이지 스마트폰이 없던 시절에는 도대체 어떤 폰을 들고다녔나 싶습니다. 아마 다시 일반 피쳐폰 쓰라고 하면 돌아버릴거예요. WM6 폰을 쓰라고 해도 목매달아버릴지도 모르지요. 아마 다들 마찬가지이실거라 생각합니다. 그런데 지금으로부터 10년전인 2002년에는 어떤 핸드폰을 사용했는지 기억나시나요? 그 당시는 폴더형의 핸드폰이 주류를 이루고 있었고 LCD가  흑백에서 컬러로 바뀌는 과도기적인 시절이였습니다. 안테나도 당연히 존재하고 있었고 1년뒤인 2003년이 되서야 안테나가 없는 핸드폰이 나오기 시작하면서 '인테나'라는 용어가 생겨나기 시작했지요. MP3 재생같은 것도 상상조차 할 수 없었고 16poly가 지원되는 것도 놀라웠던 시절이지요. 참 그시절의 핸드폰은 어떻게 사용했나 싶습니다. 정말로 긴 세월이 흘렀네요.

이미지 출처 : http://tomorrowhistory.tistory.com/

하라는 게임 이야기는 안하고 갑자기 웬 뜬금없는 핸드폰 이야기냐구요? 실은 DirectX9에 대한 이야기를 하려고 합니다. 바로 이 놀라운 핸드폰들을 사용하던 2002년에 DirectX9가 처음 배포되었지요. 그러고서는 10년이 지난 지금까지도 계속 사용되고 있는 것입니다. 지속적인 업데이트가 되면서 마이너 버젼들이 바뀌긴 했지만 DirectX9의 목숨이 계속 유지되고 있는것입니다. 게다가 여전히 대부분의 게임들이 DirectX9으로 제작되고 있습니다. 핸드폰은 흑백에서 컬러로, 16화음에서 자연음으로, 폴더폰에서 스마트폰으로 바뀌는 긴 세월 동안 DirectX9이 사용되고있는 것입니다. DirectX10이 나오고 이제 11까지도 나왔지만 여전히 DirectX9을 버릴 수가 없습니다. 아직은 대부분의 가정에 있는 PC들이 여전히 windows XP를 이용고 있고 심지어 PC방조차 마찬가지입니다. 앞으로도 당분간은 DirectX10,11로는 개발할 일이 없어보입니다. 

하지만 10년 사이에 그래픽카드 H/W는 엄청난 속도로 발전을 해왔습니다. 2002년에 3dfx의 voodoo가 단종되고 그 10년뒤인 현재는 라데온 HD 7xxx, 지포스 GTX 6xx등이 나오고 있습니다. 메모리클럭은 166Mhz에서 6000hz로 30배 이상의 성능으로 발전했습니다. 

이 부두 카드에는 슬픈 전설이 있지. 하지만 난 전설따위 믿지 않아

이토록 긴 세월동안 동일한 DirectX9 사용해왔지만 GPU는 엄청난 변화를 겪어왔습니다. 따라서 DirectX9를 사용함에 있어서도 시간이 지나면서 그 효율도 변화하게 됩니다. 예전에는 당연시 되어 왔던 최적화 방법이 현대의 그래픽카드에서는 비효율적인 최적화 방법이 될 수도 있는것이지요. 이번 포스팅에서는 DirectX9에서  최적화 시, 예전과는 다른 현대의 GPU를 위한 주의점을 정리해보고자 합니다. 사실, 별건 없고 nVIDIA, ATi, Intel의 그래픽 프로그래밍 가이드 문서의 내용들 중에서 주관적인 견해로 정리한 것입니다. 제가 자의적으로 해석하고 받아들인 내용도 있으므로 시간 되신다면 원문을 모두 읽어보시길 권장합니다. 혹시라도 틀린 내용이 있다면 지적 부탁드립니다.

nVIDIA : http://developer.download.nvidia.com/GPU_Programming_Guide/GPU_Programming_Guide_G80.pdf

ATi : http://developer.amd.com/media/gpu_assets/ATI_Radeon_HD_2000_programming_guide.pdf

Intel : http://software.intel.com/file/34436


통합셰이더모델 (unified shader model)

여기서 말하는 "현대의 GPU"의 기준을 콕 찝어 이야기 하자면 DirectX 10 이상 지원 모델으로 삼으면 될 것 같습니다. nVIDIA Geforce 8xxx 시리즈 이상, ATi Radeon HD 2xxx 시리즈 이상, Intel GMA 3xxx 시리즈 이상부터 지원되는 것으로 알고있습니다. 시기적으로는 2008년 이후정도가 되지 않을까 싶습니다. 이 때부터 DirectX10 지원 카드가 본격적으로 출시되기 시작했지요. 

이 DirectX 10 지원 여부가 그래픽 카드 아키텍쳐의 큰 변화를 가져오게 되었습니다. 바로 통합 셰이더 모델( Unified Shader Model)이지요. 예전에는 버텍스 셰이더 (vertex shader) 처리 유닛과 픽셀 셰이더 (pixel shader) 처리 유닛이 물리적으로 따로 존재했었습니다. 하지만 DirectX 10부터는 아키텍쳐가 통합 셰이더 모델로 변경되었습니다. 버텍스든 픽셀이든 지오메트리든 같은 셰이더 처리 유닛을 사용하게 되는 것이지요.  

이미지 출처 : http://vlsi2.kaist.ac.kr/research/multimedia-processor/unified-shader

예전에는 버텍스들을 VS 전용 유닛에서 처리하고 그 결과물을 여차저차해서 PS 전용 유닛에서 처리하고 래스터라이징을 처리가 되었습니다만 통합 셰이더 아키텍쳐에서는 이러한 VS와 PS의 처리 유닛이 물리적으로 구분되어 있지 않습니다.

 아~ 그렇구나~ 근데 이게 뭐 어쨌다구? 아까부터 왜 쓸데없는 소리만 지껄임? 님 장난함?

이러한 통합 셰이더 모델은 DirectX 10, 11을 지원하기 위한 아키텍쳐지만 DirectX9에서도 물리적으로는 이 통합 셰이더 모델로 작동할 수 밖에 없습니다. 그렇게 되면서 엄청난 변화가 생기게 되었고 그 변화를 적극 활용하는 것이 현대 GPU를 위한 최적화의 중요한 포인트가 되는 것이지요.  


버텍스와 픽셀의 셰이더 로드 밸런싱

통합 셰이더 (Unified Shader) 이전의 기존 모델에서는 픽셀 세이더 처리가 간단하더라도 버텍스 셰이더 처리가 무거우면 버텍스 셰이더가 병목이되었고, 반대로 픽셀 셰이더 처리가 무거우면 버텍스 세이더가 간단하더라도 픽셀 세이더가 그 발목을 잡아 병목이 일어나는 시나리오가 발생을 합니다. 일반적으로 픽셀 셰이더 처리 능력이 버텍스 셰이더 처리 능력보다 허접했기 때문에 최대한 많은 연산을 버텍스 셰이더에서 처리하고 그 결과를 픽셀 셰이더에 념겨주도록 만드는 것이 미덕이였습니다. 

하지만, 통합 셰이더 모델에서는 이야기가 달라집니다. 버텍스 셰이더든 픽셀 셰이더든 동일한 처리 유닛이 사용 되기 때문에 누가 누구의 발목을 잡는 상황이 일어나는 걱정은 하지 않아도 됩니다. GPU가 적절히 셰이더 로드 밸런싱을 조절하게 됩니다. 오히려 버텍스 세이더에서 계산해서 픽셀 세이더로 넘겨주는 정보가 너무 많으면 오히려 캐시 퍼포먼스나 대역폭 병목 문제등이 발생 할 수 있으므로 확인해가면서 적절한 밸런스를 잘 판단하고 사용해야 합니다.

이미지 출처 : http://www.mips.com/Customer_newsletter_0908/partnerShowcase.htm


VTF (Vertex Texture Fetch)

버텍스 세이더에서 텍스쳐를 샘플링 할 수 있는 기능인 VTF는 계륵같은 존재였습니다. 막상 기능은 존재하지만 버텍스 세이더에서 텍스쳐 샘플링을 담당하는 유닛이 너무 느렸기 때문에 마음놓고 사용하기가 번거로운 기능이였지요. 하지만 통합 셰이더 모델에서는 텍스쳐 샘플링 유닛이 따로 구분되어 있지 않고 사용하기 때문에 버텍스 셰이더에서 텍스쳐를 샘플링 하는 것이 부담스럽지가 않습니다.

이미지 출처 : http://developer.nvidia.com/sites/default/files/akamai/tools/files/PerfHUD6-UserGuide.pdf

 GPU Gems 3권에서는 이를 이용하여 인스턴싱을 구현하는 방법이 소개되고 있고, 마비노기2에서는 이를 이용하여 스키닝을 처리하고 있는 등 통합 셰이더 아키텍쳐의 수혜를 적극적으로 받고있는 능력 중 하나가 되었습니다.

이미지 출처 : 마비노기2 랜더링 기술, 전형규


셰이더 모델 버젼

전통적으로는 가급적이면 셰이더 모델 버젼을 낮은 것을 사용하는 것이 권장되어 왔습니다. 비록 Shader Model 3.0이 지원되는 하드웨어일지라도 가급적이면 Shader Model 1.1을 사용하거나 여력이 되지 않는다면 Shader Model 2.0을 사용하는 것이 권장되어 왔습니다. 하지만 이제는 가급적이면 높은 버젼의 모델을 사용하는 것이 권장되고 있습니다. 통합 셰이더 아키텍쳐는 높은 버젼에 적합하게 만들어져 있는 것이지요. DirectX 9에서는 4.0 사용이 불가하니 3.0을 사용하는 것이 좋겠지요. 특히 SM 3.0에서는 VPOS같은 훌륭한(?) 시멘틱을 사용 할 수 있어 연산도 줄일 수 있지요. 또한 최신버젼으 셰이더 컴파일러가 최적화가 잘 되어 있으므로 fxc 역시 최신 버젼을 사용하는 것이 좋습니다.


동적 분기 ( Dynamic branch)

전통적으로는 if 분기문을 사용하는 것은 죄악으로 여겨져 왔습니다만 현대의 GPU에서는 다이나믹 브랜치의 퍼포먼스가 훌륭하기때문에 if 분기를 적절히 잘 사용하면 오히려 퍼포먼스 향상을 꾀할 수가 있습니다. 예를들면 디렉셔널 라이팅의 연산이 필요 없는 그림자 영역은 연산을 건너 뛴다거나, N dot L 연산이 0 이하면 스페큘라 연산을 건너 뛴다거나 하는 식으로 사용을 할 수도 있겠지요.  

KILLZONE2는 정적 그림자 영역은 라이팅 연산을 건너뜁니다. 

 다만 주의할 점은, 픽셀이면 8x8=64픽셀, 버텍스면 연속적으로 64개가 분기 조건이 같아야 한다는 점입니다. 그렇지 않을 경우 캐시 퍼포먼스가 떨어져서 오히려 성능 저하를 초래할 수 있습니다. 앞서 예시를 든 N dot L 결과로 스페큘라를 건너뛰는 경우는 고주파 노말맵으로만 이루어진 장면인 경우는 부적합한 상황이 될 수도 있겠지요. 

이러한 동적분기의 성능을 활용하여 우버 셰이더로 활용 할 수도 있습니다. 여러 매터리얼을 한 셰이더로 몰아넣어 셰이더 설정 비용을 줄이는 것이죠. 다만 셰이더가 너무 길어지면 셰이더 캐시 문제가 발생 할 수도 있습니다.


64비트 텍스쳐 

현대의 GPU는 64bit 텍스쳐도 한 싸이클에 처리합니다. 메모리 용량 문제만 제외한다면 64bit HDR 텍스쳐를 사용 시 성능 저하를 걱정할 필요는 없는 것이지요. 하지만 무조건 그런 것은 아니고 fixed point 64bit는 여전히 두 싸이클이 걸립니다. floating point 64bit 텍스쳐를 사용해야 한 싸이클 내 처리가 되는 것이지요. 또한 필터링 역시 Point와 Bilinear인 경우엔 한 싸이클이지만 Trilinear인 경우는 두 싸이클이 소모됩니다. 


깊이 테스트 (Z-test)

현대의 그래픽 카드는 깊이 테스트의 성능이 매우 탁월합니다. 드라이버 자체적으로 H/W Depth stencil을 축소한 버퍼를 따로 가지고 있어 이를 통해 사전 테스트하여 테스트 비용을 줄이는 방법을 사용하기 때문이지요. nVIDIA는 "Hyper Z"라는 이름이고 ATi는 "Fine-grained Z"라는 이름으로 사용하고 있던데 결국 같은 기법인데 이름만 다르게 사용하는 것으로 보입니다.

이미지 출처 : Z-Buffer Optimizations, Patrick Cozzi

Early-Z 패스를 추가하여 Z-test를 활용할 수도 있습니다. 랜더링 패스 이전에 한 단계 추가하여 컬러 출력 없이 깊이만을 그려서 Z버퍼를 채워서 비싼 은면의 연산을 초기에 차단하는 것이지요. 오브젝트를 깊이순으로 정렬하여 앞의 오브젝트를 먼저 그려 뒤에 가려지는 오브젝트의 오버드로우를 방지할 수도 있습니다. 

이와 비슷한 컨셉으로 스카이박스(Sky Box)를 맨 마지막에 그리는 것도 생각해볼만합니다. 전통적으로는 스카이 박스를 Z-test를 비활성화하여 맨 처음 랜더링하고 그 뒤에 터레인이나 오브젝트등을 랜더링해왔습니다. 하늘이야 항상 배경으로 그려지는 것이고 굳이 쓸데없이 Z-test 처리하며 그릴 필요가 없던 것이지요. 하지만 이제는 Z-test의 처리가 고속으로 이루어지므로 이에 대한 큰 부담이 없으며 오히려 화면에 보이지 않게 될 픽셀을 일찌감치 걸러내어 픽셀 연산 비용을 아끼기 위해 하늘을 맨 마지막에 그리는 것도 좋은 방법이 될 수 있습니다.

이러한 고속의 Z-test를 위해서는 clear()가 필수적으로 따라줘야 합니다. 예전에는 화면의 모든 픽셀이 갱신되게 될 것이므로 화면의 clear를 건너 뛰는 방법도 사용해왔습니다. 하지만 현대의 하드웨어에서는 clear시 고속 처리를 위한 정보들이 초기화 되므로 씬 랜더 시작 전에 반드시 항상 clear 수행되어야 합니다. 풀 스크린 쿼드를 그리는 경우에는 해당되지 않습니다.


마치며

기존의 하드웨어와 현대의 하드웨어의 차이만을 초점으로 맞춰서 말씀드렸습니다만 좀 더 큰 시각으로 최적화에 대한 내용을 알고 싶으시면 제 블로그의 글을 참고 바랍니다. (블로그 홍보 죄송합니다 꾸벅) 근데 사실 좀 더 많은 PC를 커버하기 위해서는 이대로 하는 것에는 좀 무리가 따릅니다. 국내야 이 글이 대상으로 삼는 하드웨어가 대부분인 것으로 판단해도 무리가 없겠지만 해외 특히 동남아의 경우는 더 낮은 사양도 많을 것으로 판단됩니다. 이래도 고민 저래도 고민이죠. 최적화는 하면 할수록 골치가 아픈 것 같습니다. 모두들 스트레스 받지 마시고 멘탈 보호해가면서 개발하세요. 꾸벅

제 블로그의 예전 자료 :  http://ozlael.egloos.com/3671648  


반응형
,
Posted by 알 수 없는 사용자

안녕하세요. 게임기획자 주민석 입니다.

캐릭터 파라미터, 몬스터 파라미터, 퀘스트 파라미터 등 DB에 저장할 필요 없고, 값이 고정적인 파라미터(일시적으로 변동 되긴 하지만 DB에 저장 될 필요 없는)를 FDB(Fixed Data Base)라고 부르고 있습니다. 보통 이런 FDB를 게임에 적용하는 방법은 기획에서 엑셀로 데이터를 작성하고, 규약 된 XML로 추출하여 파싱하거나, CSV 파일 형태로 사용하는 방법도 있죠. 보통 FDB의 작성과 관리는 리드 프로그래머의 취향에 따라 결정 되는 것 같습니다.

오늘 말씀 드릴 내용은 제가 두개의 MMORPG를 개발하면서 사용했던 FDB의 작성 방법에 대해서 입니다. 보편적으론 XML을 많이 사용하는 것으로 알고 있는데, 저는 Config 라고 부르는 형태의 파서를 통해 작업 하였습니다.

제가 초짜 기획팀장으로 MMORPG를 개발할 때 서버 엔진이 Config라는 형태로 데이터를 파싱하도록 구현 되었고, 저는 그 뒤로 10년을 같은 서버, 클라이언트 프로그래머 분들과 함께 해왔기 때문에 선택의 여지 없이 Config라는 형태로 데이터를 작성하게 되었습니다.

지금은 XML을 사용해서 작업하고 있는데 Config와 비교해서 무엇이 좋다. 어렇게 해야한다. 하는 내용은 아닙니다. 

'아~ 이렇게 개발하는 사람도 있었구나' 정도로 봐주세요.

 

1. Config의 기본 형태

제일 간단한 형태부터 예를 들어 설명 하자면

힘 : 50 / 민첩 : 25 / 지능 : 15 / 체력 : 80 / 마력 : 40 의 파라미터를 가진 인덱스 1번의 몬스터가 있습니다.

이 것을 보통 XML로 표현하면

<mob>

<index>1</index> 

<str>50</str>

<dex>25</dex>

<int>15</int>

<hea>80</hea>

<mana>40</mana>

</mob>

이렇습니다. 이 것을 Config는 이렇게 표현 합니다.

( inxdex 1 (str 50) (dex 25) (int 15) (hea 80) (mana : 40) )

(필드명 필드값) 이 기본 구조입니다. 괄호는 시작과 끝을 알려주는 중요한 구분입니다.

하나의 몬스터의 파라미터임을 정의해주기 위해 ( index 값 (필드명 필드값) ... ) 이렇게 대괄호로 묶어주었습니다.

괄호 열기와 괄호 닫기는 파싱의 기준이 되므로 꼭 쌍이 맞아야 합니다.

 

2. Config의 응용 형태

Config의 기본 형태는 (필드명 필드값) 이지만 (필드명 필드값 필드값) (필드명 (필드값) (필드값)) 등으로 상황에 맞춰 응용하여 사용할 수 있습니다. 물론 프로그래머와 값 입력 방식을 사전에 규약해야 합니다.

예를 들어 최소공격력 40, 최대공격력 50 인 몬스터를 표현하면

(minattack 40) (maxattack 50) 이렇게 할 수도 있지만,

(attack 40 50) 또는 (attack (min 40) (max 50)) 등으로도 작성할 수 있습니다.

규약의 기준은 어떤 방법으로 작성하는 것이 개발하기 편리하고 추후 가독성이 높은지를 고려하여 결정하였습니다.

 

3. Config 작성 및 추출 방법

마찬가지로 엑셀을 사용합니다. 

기본적으로 두개의 시트를 사용하는데, 첫번째 시트는 기획자들이 데이터를 입력하는 시트 입니다. 이 시트의 구성은 기획자들이 보기 편하고, 입력하기 편하게 구성 합니다. 두번째 시트는 첫번째 시트에서 입력 된 값을 합수를 이용해 ( 필드 값 (필드명 값) (필드명 값) (필드명 값 값) (필드명 (값) (값) ) 형식의 Config로 전환해 주는 시트 입니다. 서버 혹은 클라이언트로 Config를 넘길 때는 두번째 시트를 전체복사 하여 txt 파일에 붙여넣기 한 뒤 전달하는게 제가 그동안 사용한 FDB 제작 공정 입니다.

순서대로 스샷과 함께 다시 정리 해보겠습니다.

a. 엑셀에서 데이터를 입력 

위 스크린샷과 같이 데이터를 입력합니다. 이 시트는 기획자가 수치를 입력하기 편리하게 제작했습니다. 시트에 밸런스를 확인하기 위해 최종 Config로 뽑지 않아도 되는 값, 기획적 분류를 위한 값들을 포함하고 있어도 무관합니다.

b. 액셀에서 수식을 이용 Config 형태로 변환

선택 된 셀을 보시면 IF, ISBLANK 함수를 활용하여 (sta 46)의 필드와 필드값을 가진 Config로 변환 된 것을 보실 수 있습니다. 이렇게 첫번째 시트의 값을 함수를 이용하여 두번째 시트에서 Config에 정의 된 패턴으로 변환 합니다.

아래 시트명을 보시면 'Parameter'가 기획자들이 작업하는 시트입니다. initnpc는 parameter에서 npc 능력치 및 파라미터 정보와 관련 된 값들을 Config로 전환한 것입니다. TW_initnpc는 parameter에서 대만에 서비스 될 npc의 능력치 및 파라미터 정보를 Config로 전환한 것입니다. NPCname은 parameter에서 npc의 이름과 설명에 관한 정보만 별도의 Config로 전환한 것입니다.

보시다시피 기획자가 하나의 시트에서 통합적으로 관리하여 여러 종류의 Config로 출력할 수 있도록 하였습니다.

c. txt 파일로 저장

엑셀의 Config 시트를 전체복사+카피 하여 txt 파일에 전체 붙여넣기 하고 txt 파일로 저장한 뒤에 서버/클라이언트에 전송하면 끝.


4. Config 제작에 사용 된 엑셀 함수

위에 보여진 Config에는 엑셀의 IF 함수와 ISBLANK 함수를 동시에 사용하였습니다.

보통 =IF(ISBLANK(Parameter!A2), "", "("&Parameter$A$1&" "&ParameterA2&")" ) 형태의 함수인데 ISBLANK는 괄호 안에 지정 된 셀이 공백인지 확인하는 함수 입니다. 만약 공백이라면 IF에서 참으로 "", 즉 공백을 현재 셀에 반환할 것이고 지정 된 셀에 값이 있다면 거짓으로 (필드명 필드값) 형태로 값을 반환하여 현재 셀에 입력하라는 내용입니다.

Parameter 시트에서는 한글 혹은 영문으로 표기해야 하고 Config에서는 숫자로 출력해야 하는 경우가 있습니다. 예를 들어 NPC의 종족 구분 같은 경우인데 Parameter 에서는 기획자들이 확인하기 쉽게 사람, 동물, 귀신 등으로 작성하고 싶지만 Config에서는 (species 1), (species  2) 등으로 규약 된 경우 입니다. 이 때는 vlookup 을 이용해 간단히 해결할 수 있습니다.

 

5. 마치며

Config에 대한 장단점은 언급하지 않겠습니다. 제가 이 방식을 10년 고수해왔기 때문에 다른 방법보다 Config가 익숙하고 편하기 때문에 장단점을 비교하기는 어려울 것 같습니다. 어느덧 100라인이 넘는 Config를 엑셀 도움 없이 text 편집기로 바로 작업해도 괄호 열고 닫고 짝 맞춤도 틀리지 않고, 1000라인 넘어가는 Config도 뻑이나면 눈대중으로 훑어 보고 오류 지점을 찾아내는 생활의 달인급 경지에 올랐거든요. ㅡ,.ㅡ;;;

죽 써놓고 보니 오늘 내용은 참 별거 없네요. ㅎㅎㅎ

 

** 위 문서 스크린샷은 현재 국내 서비스가 종료 된 SP1 Online의 실무에 사용 되었던 데이터 입니다. SP1 Online 국내 서비스가 종료 되었고, 창업 멤버로 참여했던 실버포션도 폐업 하였기에 데이터 일부를 지식 공유 차원에서 공개하였습니다. 속쓰리네요. T,.T

반응형
,
Posted by 알 수 없는 사용자

건축의 역사 - 중력에 저항하다. III

안녕하세요 T모사에 이제 더이상 없을수도 있는 배경아티스트/TA Silverchime입니다 
저번에 서양건축사의 아버지뻘 되는 그리스의 건축에 대해 알아 보았지요? 

오늘은 에트루리아-고대로마-로마건축 에 대해 알아보겠습니다.

BC 900년경 가장 문명화된 곳은 어디였을까요? (다른 문화의 타임라인을 언급하지 않기로 했지만...)  
놀랍게도 북아프리카와 아시아였습니다. 

기이한 크레타/미노아 문명을 제외하면 말입니다 

<크레타 섬에 존재하던 미노아 문명>

(BC 3500-1500년 경에 이미 수세식 변기와 환기시설 , 수도시설, 하수 시스템, 
심지어 비행 머신까지 만들었다고 전해지는 문명입니다. 본격 외계인 개입설.)

미노아 주거의 천정Lightwell 위쪽이 뚫려 있어 빛이 아래층까지 들어옵니다. 
저 벽화도 복원한 겁니다. 원래 아티스트들이셨슴...

기원전 3-2천년 정도에 석재로 이삼층 집을 짓고 이러고 살았다니 -_-a  놀랍지 않습니까

그레이트 마징가의 적도 궂이 말하자면 
미케네가 아니라 미노아Minoan 또는 크레타Creta 문명이라 함이 좀더 납득이 갈  것입니다.

이런 당시 상상할 수 없었던 수준의 문명이 화산 폭발로 하루아침에 멸망한것도 미스테리이지요

이런 대재앙을  보고나서 신을 믿지 않는 사람들은 없었을 겁니다. 
폐허가 된 뒤 미케네의 빈집털기...미케네-그리스(에인션트 그리스) 로 이어지는 형태지요. 

그러나... 위에 건축에서  보면 약간 아쉬운 부분이 있습니다. 
이제 그 뭔가(?)아쉬운 부분에 대해서 알아보도록 하겠습니다. 


에트루리안 문명 Etruscan civilization (B.C. 768 - )

유럽은 상대적으로 많이 뒤떨어져 있었지요. 그러나 이탈리아 서쪽 해안을 따라 정착한 에트루리아인들이 이 문명의 흐름을 바꾸기 시작합니다. 이 사람들이 어디에서 왔는지는 여러 설이 있지만, 토착민들이 그리스 쪽에서 지식을 들여와서 발전했다 라는 설이 지배적입니다. 그릭 알파벳을 썼지만, 말은 자기들 특유의 언어를 구사하였습니다. 적어도 손재주와 장사, 그리고 머리가 좋았던 것은 확실합니다.

어째서  에트루리아를 언급하느냐고 하면, 이 에트루리아 인들이 이탈리아 중부(로마의 중앙북부  토스카나 지방, 사실상 Tuscany는 Etruscan에서 기원된 단어 입니다. ) 에서 가장 일찍 도시국가를 세우고 문화를 발전시켜 나아간 문명이기 때문입니다. 이탈리아 반도에서 가장 일찍 건축문화를 꽃피운 문명이기도 합니다. 

(그리스의 에게 해 문명과는 약간 서쪽에 위치해 있습니다)

역사의 포커스가 서지중해로 이동합니다.

에트루리아 역시 BC 700-800년 정도에 적극적인 식민/영토확장을 시도했고, 
결국 기존의 세력 그리스-페니키아인들과 충돌을 면할 수 없었습니다. 

피할수 없는 충돌의 결과로  알랄리아 해전Naval Battle of Alalia 에서 
에트루리아+카르타고 연합함대가 그리스 함대를 격퇴한 후
에게해에서 서지중해로 역사의 저울이 기울기 시작했습니다.
이탈리아 서쪽의 두 섬 중 코르시카는 에트루리안 손에, 사르데냐는 카르타고의 손에 들어갑니다. 

에트루리아 인은 상업도시로서 개방적인 문명을 이루었으며,
또한 에게해  문명을 적극적으로 받아들이기도 하였습니다.
이로 인해 신전 건축등이 영향을 많이 받았지요. 
 

또한 멋진 건축적 공헌은 , 바로 혁명적인 배럴 볼트와 아치를 사용함에 있었습니다.

건물에 가장 쉽게 개구부(開口部)를 내는 방법은 바로 다음과 같은 형태입니다.

상인방Lintel으로 상부의 무너지는 것을 방지하고 아래로 통로나 창을 내는 것이 가장 기본이지요.
그러나 이 방법은 위에 큰 무게(하중)이 실릴 경우, 쉽게 부러지기 때문에 큰 너비의 통로를 내기가 쉽지 않다는 약점을 가집니다. 

그러나 아치의 발명으로 인해 비교적 작은 돌들로 반원형태의 아크를 만들기 시작한 후,
이 혁신적인 공법은 훨씬 더 넓고 높은 개구부를 가능하게 하였고, 심지어, 더 큰 하중을 받아낼 수 있게 되었습니다. 

아주 작고 형태성의 아치는 수메르인도,  그리스인도, 마야인도  변칙적인 아치의 형태를 만들어 냈습니다만, 
에트루리안들이야말로 수직방향의 무게를 측벽으로 분산하는 제대로 된 아치구조의  컨셉트를 정착시킨 장본인이라 할 수 있습니다. 

우측 : <The 'Cloaca Maxima' BC 600>

그들은 아치를 연속해서 나열할 경우 천정이 동그란 형태의 복도를 만들 수 있다는것을 생각해 냈고,
위와 같은 때의 로마 최초의 볼트를 이요한 하수 시스템을 에트루리안들이 디자인 하였습니다.  
이 유용성은 그야말로 혁명과 같아서, 아치는 모든 류의 거리와, 빌딩 등에 급속히 보급되기 시작하였습니다. 


로마 문명 ( 전설에 따르면 : BC 753~ )

로마의 시조라고 전해지는 로물루스와 레무스의 동상.
저때만해도 사이가 그리 좋았었는데...

에트루리아 남쪽에 척박한 땅의 작은 도시국가 로마가 이렇게 커질줄 아무도 예상하지 못하였을 것입니다. 

로마는 문화적으로 에트루리안들에게 어마어마한 영향을 받았으며,
BC509년까지 에트루리아 왕의 지배를 받았습니다. 폭정 왕정을 벗어나, 드디어 로마 공화정의 시대가 시작됩니다.  

으아아아 공화정 출범이요

그 작던 에트루리아 남쪽 척박한 위치에 있던 작은 도시국가 로마가 
다음과 같은 방대한 제국령을 차지하게 됩니다. 

아래로는 이집트부터, 위쪽으로는 브리튼까지. 
이때는 대서양 동쪽으로는 지옥이 있다고 생각했고, 
사실상 인지하던 유럽 세계를 거의 정복했다고 생각했을 것입니다. 

각지에서 몰려드는 재화와 정복지의 노예들로 로마는 이례없는 영광과 사치의 시대를 누리게 되고
그 재력과 노동력을 기반으로 건축의 대 혁명기를 맞이하게 됩니다.

로마에 대한 역사를 나열하면 한도끝도 없으므로, 몇가지 키가 되는 문명과 기술에 대하여 알아보겠습니다. 


재료적 측면 : 콘크리트의 발견

콘크리트로 만든 로마시대 볼트.

이태리 포졸리pozzoli라는 마을에 이것이 그리 많았다고 하네요. 화산회Pozzolana

로마시대에 사용된 콘크리트는 생석회quicklime와 화산회pozzolana에 물을 추가하고 골재를 섞어 만든 수경콘크리트를 사용하였습니다.
가히 건축 재료의 혁명이라 할 수 있었지요. 아치를 360도 회전시켜 만든 돔도 이것으로 만들어 졌습니다.
이 콘크리트를 보강재 또는 접착제로 사용할 수 있었던 배경이 있지 않았다면 수많은 건축물의 구현이 불가능했을 것입니다.


볼트와 교차 볼트

볼트를 서로 교차시키면 교차볼트Groin Vault/Cross Vault가 됩니다. 

그리스의 신전은 여기서 평면의 약간의 진화를 통해 바실리카 Basilica로 변하는데
이 내부 천정부분이  바로 이 볼트와 크로스볼트로 구성되게 됩니다. 

성 베드로 바실리카 Saint Peter Basilica.
천정부가 볼트로 이루어져 있음을 알 수 있습니다. 

크로스볼트의 연속으로 이루어진 바실리카 측랑(측면 복도)


아치와 돔

다음으로 아치를 중심선에서 180도 회전하면 돔을 만들 수 있습니다. 

가장 유명한 로마시대의 돔 건물은 판테온이겠지요. 

실제로 가서 봐야 되는데 말이죠. 

가운데 있는 구멍은 와류로 인해 비가 내리는 날씨에서도 비가 들이치지 않는다고 합니다. 

현존 철근 등으로 보강되지 않은(Unreinforced) 
순수 콘크리트로 세계 최고 규모의 돔입니다.


바실리카 Basilica

바실리카는 고대 로마에서부터 시장market(실내)과 법정court을 겸한 공공건물로서 존재하였습니다. 

 커다란 실내 공간이 필요했던 당시의 요구에 부응하여, 이전 단계의 가장 커다란 실내공간이었던 
신전건축에서 아이디어를 얻지 않았을까 추측해 봅니다. 

이 내부의 공간배열은 대대로 이어져 성당 건축의 모태가 됩니다.

신이 아닌 인간이 내부 공간을 잠시 차지하게 됩니다. (포럼, 마켓, 기관 등)
곧 다시 성당건축으로 신에게 돌아가지만 말입니다.

물론 처음부터 이랬던건 아니었고, 
초기 바실리카는 다음과 같은 형태였습니다. 

천정이 볼트가 아닌 목조 가구식 지붕으로 되어 있는것을 알 수 있습니다.

내부에서 보면 이런 형태입니다.
로마 후기-로마네스크로 가면서 이제 저 부분이 볼트로 바뀌게 됩니다. 


수로시설 Aqueduct

로마가 도시문명을 한세대 더 높이 끌어올릴 수 있었던 것 중 하나입니다. 
도시기능중에 가장 핵심인게 뭘까요? 상하수도, 전기, 인터넷(퍽)

네 다시 말해서 인프라입니다. 대부분의 로봇들이 해야 할 일은 
현존 개발된 어느 안드로이드보다도 훨씬 더 다양한 일을 할 수 있는 
정복지에서 잡아온 노예들이 있었으니

당시의 로마 사람들을 불편하게 살았다고 여기시면 안됩니다. 

당시에 이런 복장이 개발되지 않았다는것이 참 안타깝습니다.
(물론 저는 노예제를 반대합니다!)

각설하고, 로마는 아치를 이용하여 할로우텍(?) 공법으로 (그걸 다 석벽으로 쌓았다고 하면 끔찍하겠죠)
긴 수로를 도시안으로 끌어들였습니다.


정수시설과 펌프를 빼면 사실상 현대와 똑같습니다. 
에너지 친화적이라는 면에서는 좀더 나을수도 있겠네요.

(그러나 미노아 문명에서는 이걸 이미 BC 2000년도에 이룩해 냈습니다 -_-
4천년전이라고요 
로마 3등! 2등은 바빌로니아 공중정원.)

그 규모는 심히 창대하여, 모든길은 로마로 통한다는 말도 지만
모든 도회에 이 수로Aqueduct도 뻗어 있었지요. 


하수도는 위의 에트루리안 하수도를 봅시다. 에트루리안 기술자들은 로마에 기술을 전파했으며
최초의 로마 하수도 디자인도 이 사람들이 하였습니다. 

규모도 커서 안으로 사람들이 다닐 수 있었지요. 사람들이 숨어다니기도 하고 
나중에 지하무덤 Crypt와 함께 던전Dungeon의 모티브가 되기도 합니다. 


목욕탕 Roman Bath

자 수도시설도 있겠다 도시위생을 위해 발전된 시설이 목욕탕입니다 
기본적인 구조는 바실리카와 같습니다만, 위에 뚜껑을 덮을 필요가 없었으므로
(가운데 큰 공간을 신랑  Nave라고 합니다) 신랑 공간이 더 넓을 수 있었지요.

Roman Bath

고대 로마의 목욕문화에 대한 기록입니다. 
그러고 보면 최고의 서비스 기계는 바로 사람의 손 이네요.


개선문 Triumphal arch 

아치와 볼트의 응용형이라 볼 수 있습니다.


콜롯세움 Colosseum

대단해 보이지만 외벽은 결국 수로Aqueduct를 원형으로 돌린 것입니다. 
(이렇게 간단하게 넘어가도 되는건가...자네)

그외에도 여러 로마시대의 혁신적인 건축물이 많지만, 
건물 하나당 사실 다 컬럼 하나는 나올거에요 ^^; 헉헉헉 여기서 대강마무리 짓겠습니다.
(이 글은 아무래도 공학적 글이니까요..)


결론 

에트루리아-로마로 이루어지는 로마 시대의 건축문화는

에트루리안- 아치의 기본컨셉인 

수직하중을 홍예석 voussoir을 통해 다른 쪽으로 분산한다-는 

진정한 아치의 개념을 확립하였으며 

로마에서는 콘크리트라는 재료의 발견과, 풍부한 자금과 노동력을 베이스로 하고
아치를 응용한 기술로서 볼트Vault, 그리고 교차 볼트Cross Vault/Groin Vault를 사용함으로서
현대 유럽 건축의 기본이 되는 구조체를 발전시키며 거대하고 아름다운  건축문화를 꽃피울 수 있었습니다. 

그리고 이집트-그리스에서 이어진 신전의 계보는 바실리카를 중간 매개체로 하여 중세 고딕 성당건축의
기본적인 평면과 수직으로 높고 수평으로 넓은 공간에 대한 도전의 가능성을 열게 됩니다. 

천정의 높이도 높아졌을 뿐더러, 내장공간의 스팬도 넓어지고,
들어올려진 천정의 사이간격에 창문을 냄으로서  실내공간에 빛을 끌어들이게 되었습니다. 
(이는 차후 스태인드 글라스로 이어집니다)


자, 한번 생각해 볼게 있어요

로마나 에트루리아 이전의 문명을 게임배경으로 할때
아치나 볼트와 같은 구조체를 배치한다던가, 바실리카와 같은 거대한 건물을 넣으면 어떤 문제가 생길까요?
다른건 몰라도 그런게임을 유럽쪽에 퍼블리싱 할려면 문제가 생기겠지요.


이런 배경을 하려면, 어떠한 마법이나, 판타지 종족과 같은 일종의 기믹등이 없다면
아마 그 게임 내의 문명이나 환경에 대하여 설득력을 가지기 힘들것 같다는 생각이 드네요.

다음은 고딕 건축.

신성하고 높은 공간에 대한 동경,
중력에 맞서 불가능을 현실로 만드는 사람들의 이야기를 해보도록 하겠습니다. 


요즘 이런저런 개인적인 일이 많아 업데이트가 좀 늦었습니다. 
다른 모든 분들께는 저에게와 같은 일이 생기지 않도록 기원하겠습니다.

그래도 웃음과 여유를~! ^^

즐거운 목요일 되십시요.





반응형
,
Posted by 알 수 없는 사용자

안녕하세요? Rhea입니다.

누군지 모르신다고요?

 

 

아...흠....그러니까..... (;゜∇゜);;;;;;;;

한때 네트워크 강좌를 적다가 잠적했던 백수 히키코모리인데요...

그것도 추가 강좌를 적겠다고 맘먹고 놀기 바빠 사라졌는데요......

 

 

 

 

 

 

 

 

 

으으윽 잘못했습니다. ㅠㅠ

성실하게 살겠습니다.

 

때리시면 맞겠습니다... ㅠㅠ때리시면 맞겠습니다... ㅠㅠ

 

지난 시간 Serialize, 직렬화에 대해 언급했습니다.

그리고 직렬화를 이뤄지는 라이브러리에는 상용도 있고 공개된 버전도 있다고 하였습니다.

물론 Boost.Serialization을 잊지 말아야겠죠, 이 강좌에서 사용하는 엔진이 ASIO이고 ASIO는 Boost 답게 Boost.Serialization과 멋진 궁합을 보여줍니다.

 

1. 패킷 정의

이번에 보내볼 패킷은 다음과 같습니다. 대강 그럴싸하게 만든 엉터리 패킷입니다. (* ̄∇ ̄)/

//

// MySerializePacket.h

//

 

class ANS_LOGIN
{
public:
 // 로긴성공
 BOOL isOK;
 // 사용자 식별번호
 UINT uUserSerialNumber;
 // 사용자 이름
 string strName;
 // 경험치
 long lExp;
 // 레벨
 UINT uLevel;
 // 소유 장비
 map<long, long>  mapEquipment;
 // 친구목록
 vector<UINT> vtGuildFriend;
}

 

이것은 로비에 접속되었다고 가정했을때, 로긴 성공 메시지로 알려주는 내용입니다. BOOK isOK로 성공했다고 알려주고, UINT uUserSerialNumber 라고 사용자 식별번호도 알려줍니다.

 

UINT uUserSerialNumber 같은 INT형 사용자 식별번호는 시중의 책에서는 거의 나오지 않습니다(아니 그전에 로긴 방법을 소개하는 게임 제작 책이 있던가요?) 예전에 만든 아바타 채팅에서는 단순하게 사용자별 Key값을 단순히 ID로 했습니다. 그러나 실제 서비스되고 있는 상용 서버(게임 이외에도) 내에서는 string 형태의 ID를 사용하지 않고 INT형으로 분류합니다.

 

string에서 INT로 바뀌면 개발자에게 상당히 편합니다. 개발자 뿐일까요, string으로 검색하지 않고 INT로 검색하니 컴퓨터에게도 빠르고 좋습니다. 당연히 DB에서도 Primary Key값은 이 UserSerialNumber 같은 INT값입니다!!! 이렇게 짤때 따라오는 코딩의 편리함에 대해서는 따로 이야기하지 않겠습니다. 여기에 그치지 않고 서비스의 성격부터 바꿀수 있습니다.

 

이것에 대한 예로는 트위터를 들까 합니다.

트위터는 표시되는  프로필이름(대화명)을 바꿀수 있습니다.

계정 메뉴에서 아이디를 바꿀수 있습니다.

그리고 무려 이메일 주소도 바꿀수 있습니다!!!

그럼 사용자 구분은 뭘로 할까요, <ID>라는 별도의 INT형(실은 LONG 정도 되겠죠?) 필드로 따로 관리합니다. 물론 사용자에겐 보여지지 않습니다.

 

JAWITTER = Joint Assault Windows Interface twiTTER 라는 4달째 개발JAWITTER = Joint Assault Windows Interface for twiTTER 라는 4달째 개발"중"인 툴입니다. 필자의 ID가 보입니다.

모든 멤버쉽 서비스는 이렇게 돌아갑니다. 이메일이나 ID를 변경하느냐 못하느냐는 기획과 사업적인 문제이지 개발적인 문제가 아닙니다.

 

다음을 보죠, 드디어 STL string이 나타났습니다. char에 담은게 아닙니다. 그리고 장비목록은 STL map에 담았고 친구리스트는 STL vector에 담았습니다.

과연 제대로 날라갈수 있을까요?

 

2. Boost.Archive

Boost.Serailize의 컨셉은 Boost.Archive란 유틸리티 클래스에 기반합니다.

Archive는 직렬화를 하는 text_oarchive 클래스와 풀어주는 text_iarchive 로 구분됩니다.

이건 코드를 보는 편이 훨씬더 빠릅니다.

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <fstream>

 

void save()
{
  std::ofstream file("archiv.txt");
  boost::archive::text_oarchive oa(file);
  int i = 1;
  oa << i;
}

 

void load()
{
  std::ifstream file("archiv.txt");
  boost::archive::text_iarchive ia(file);
  int i = 0;
  ia >> i;
  std::cout << i << std::endl;
}

 

int main()
{
  save();
  load();
}  

 

간단한 파일 세이브/로드지만 막강합니다. << 로 파일 스트림으로 쓰고 >>로 파일 스트림에서 읽어왔습니다.

 

이제는 일반 변수가 아닌 클래스를 갖고 놀아보죠. 왜냐면 패킷도 클래스이기 때문입니다.

#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <iostream>
#include <sstream>

std::stringstream ss;

class person
{
public:
  person()
  {
  }

  person(int age)
    : age_(age)
  {
  }

  int age() const
  {
    return age_;
  }

private:
  friend class boost::serialization::access;

  template <typename Archive>
  void serialize(Archive &ar, const unsigned int version)
  {
    ar & age_;
  }


  int age_;
};

void save()
{
  boost::archive::text_oarchive oa(ss);
  person p(31);
  oa << p;
}

void load()
{
  boost::archive::text_iarchive ia(ss);
  person p;
  ia >> p;
  std::cout << p.age() << std::endl;
}

int main()
{
  save();
  load();
}

 

person이라는 클래스를 직렬화했습니다!!

이것을 해주는 것이 Boost안의 serailization.hpp의 인라인함수인 serialize()입니다.

 

//

//  serailization.hpp

//

 

// default implementation - call the member function "serialize"
template<class Archive, class T>
inline void serialize(
    Archive & ar, T & t, const BOOST_PFTO unsigned int file_version
){
    access::serialize(ar, t, static_cast<unsigned int>(file_version));
}

 

이를 위해서는 person안에 serialize()를 오버라이딩해줘야 합니다.

이 모습은 우리에게 친숙한 MFC에서도 발견할수 있습니다. MFC의 맨상위 클래스인 CObject를 보시면 Serialize()이란 가상함수가 존재하고 있음을 발견하게 됩니다.

//

// afx.h

// 

 virtual void Serialize(CArchive& ar);

이는 MFC에서 상당히 중요한 의미를 가지는데 MFC의 모든 클래스에서 직렬화를 구현할수 있다는 의미입니다.

Boost에서는 따로 선언을 해줘야 합니다. 위에서는 friend class boost::serialization::access;로 끌어댕겼습니다.

 

여기서 잠깐!

 

참고로 제가 이렇게 가끔 MFC로 설명하는 경우가 있지만 MFC광팬은 아닙니다.

지금보면 MFC는 Native C++과 생소한 Windows 자료형, 그리고 리소스가 당시의 부족했던 기술로 조합하거나 너무 큰 기술로 결합되어 있고 지금도 그 Legacy가 제대로 걷혀지지도 못했습니다.

누군가 제 블로그를 MFC 예제 사이트라고 오래 전에 멋대로 적은 바람에 그 이후엔 블로그에서는 MFC 사용도 안하고 있습니다.

 

다만 MFC은 무려 1992년에 나온 거의 최초의 상업용 C++ 클래스이자 프레임워크입니다(볼랜드는 OWL).

그안에 들어간 개념과 철학들은 C++의 역사라고 봐도 무방합니다.

그리고 후대의 많은 프레임워크들에게 엄청난 영향을 주었습니다. 아이폰 앱을 만드는 Cocoa 프레임워크도 MFC의 영향, 엄~~~~~~~~청나게 받았습니다.

 

제가 말씀드리고 싶은 것은 바로 코앞에 있는 MFC의 개념을 잘 이해하면 자신만의 프레임워크를 만들기도 편하고 다른 프레임워크를 이해하기도 무척 편하다는 말입니다.

MFC가 없었으면 C++도 없었어!!MFC가 없었으면 C++도 없었어!!

 

 

 

 

그러니 광팬은 아니지만 MFC 너무 까지마염 ㅠㅠ 조만간 예제 코드 WTL로 바꿀꺼임.................무슨 말을 할려고 박스까지 쳤지는 까묵!!!

 

 

3. Asio.Serialization

직렬화의 비밀은 Archive에 있다고 알려졌습니다. 이제는 Archive를 ASIO, 즉 소켓으로 보내볼 차례입니다.

 

 

코딩에 앞서 뿌니뿌니~♡코딩에 앞서 뿌니뿌니~♡

 

먼저 패킷 클래스에 직렬화 함수를 추가합니다.

//

// MySerializePacket.h

//

 

class ANS_LOGIN
{
public:
 // 로긴성공
 BOOL isOK;
 // 사용자 식별번호
 UINT uUserSerialNumber;
 // 사용자 이름
 string strName;
 // 경험치
 long lExp;
 // 레벨
 UINT uLevel;
 // 소유 장비
 map<long, long>  mapEquipment;
 // 친구목록
 vector<UINT> vtGuildFriend;

 template <typename Archive>
 void serialize(Archive& ar, const unsigned int version)
 {
  ar& isOK;
  ar& uUserSerialNumber;
  ar& strName;
  ar& lExp;
  ar& uLevel;
  ar& mapEquipment;
  ar& vtGuildFriend;
 }

};

 

후훗, 추가되었습니다.

 

다음은 핵심인 직렬화 과정입니다.

이번 회의 소스는 ASIO 예제사이트인 http://www.boost.org/doc/libs/1_48_0/doc/html/boost_asio/examples.html 에 있는 Serialization 항목에서 가져왔습니다. 그런데 해당 예제는 서버에 Session 클래스가 없습니다. 실제 세션을 열어 데이터를 주고 받을 수 없기 때문에 http://www.boost.org/doc/libs/1_48_0/doc/html/boost_asio/example/serialization/connection.hpp 을 가져와 CSerializeEngine이라는 이름을 붙였습니다. 무려 클래스명에 Engine씩이나 붙인 이유는 이 정도 작업을 해주는 클래스는 정말로 서버 Engine 레이어이기 때문입니다.

다음은 실제 패킷을 직렬화하고 소켓으로 보내는 부분입니다.

 

  /// Asynchronously write a data structure to the socket.
  template <typename T, typename Handler>
  void async_write(const T& t, Handler handler)
  {
    // Serialize the data first so we know how large it is.
     std::ostringstream archive_stream;
    boost::archive::text_oarchive archive(archive_stream);
    archive << t;
    outbound_data_ = archive_stream.str();

    // Format the header.
    std::ostringstream header_stream;
    header_stream << std::setw(header_length)
      << std::hex << outbound_data_.size();
    if (!header_stream || header_stream.str().size() != header_length)
    {
      // Something went wrong, inform the caller.
      boost::system::error_code error(boost::asio::error::invalid_argument);
      socket_.get_io_service().post(boost::bind(handler, error));
      return;
    }
    outbound_header_ = header_stream.str();

    // Write the serialized data to the socket. We use "gather-write" to send
    // both the header and the data in a single write operation.
    std::vector<boost::asio::const_buffer> buffers;
    buffers.push_back(boost::asio::buffer(outbound_header_));
    buffers.push_back(boost::asio::buffer(outbound_data_));
   boost::asio::async_write(socket_, buffers, handler);
  }

 

이제까지와 마찬가지로 앞부분에 헤더를 붙이는 과정을 일단 생략하면 Boost.Serialzation 예제와 똑같습니다.

직렬화로 보내고 받는 함수는 Session 클래스에 있던 함수 대신 CSerializeEngine에 있는 함수들을 써야 합니다.

그래서 실제로 이 부분은 서버의 Engine 레이어라는 것입니다.

 

ANS_LOGIN 패킷을 꼭꼭 채워봅시다.

 

//

//  RheaGameSession.cpp

// 

 ANS_LOGIN ansLogin;

 

 ansLogin.isOK = TRUE;
 ansLogin.uUserSerialNumber = 10001;
 ansLogin.strName = _T("레아스트라이크");
 ansLogin.lExp = 68000L;
 ansLogin.uLevel = 99;

 

 ansLogin.mapEquipment.insert(Long_Pair(1, 101));
 ansLogin.mapEquipment.insert(Long_Pair(2, 102));
 ansLogin.mapEquipment.insert(Long_Pair(3, 103));
 ansLogin.mapEquipment.insert(Long_Pair(4, 104));
 ansLogin.mapEquipment.insert(Long_Pair(5, 105));

 

 ansLogin.vtGuildFriend.push_back(10011);
 ansLogin.vtGuildFriend.push_back(10012);
 ansLogin.vtGuildFriend.push_back(10013);
 ansLogin.vtGuildFriend.push_back(10014);
 ansLogin.vtGuildFriend.push_back(10015);

 

 AnsLoginVector.push_back(ansLogin);

 

m_connection.async_write(AnsLoginVector, boost::bind(&CRheaGameSession::handle_write_serialization, this, boost::asio::placeholders::error ));

 

string은 변환없이 string으로 채웠고 map과 vector에도 데이터를 넣었습니다.

앞서 말한대로 CSerializeEngine::async_write()를 통해 데이터를 보냅니다. ASIO에서는 데이터를 보낼때 기본적으로 vector에 담아 보냅니다. 이는 대단히 편리한데 만약 여러사용자에 대한 데이터를 보낸다면 그대로 vector에 담아 보낼수 있겠죠.

 

STL 컨테이너들의 실제 Archive는 여러 파일에 나눠져 있습니다. \boost\boost_1_47\boost\serialization 폴더에 보시면 보낼수 있는 컨테이너들이 들어있습니다. 정말이지 이런 것을 공짜로 작업하신 훌륭하신 분들에게 감사의 말씀을 드립니다.

 

실제로 데이터가 갈까요?

결과를 확인해보죠.

 

//

// ClientSocket.h

// 

 void handle_read(const boost::system::error_code& e)
 {
  if (!e)
  {
   // Print out the data that was received.
   for (std::size_t i = 0; i < AnsLoginVector.size(); ++i)
   {
    BOOL bIsOK           = AnsLoginVector[i].isOK;
    UINT uUSN            = AnsLoginVector[i].uUserSerialNumber;    
    string strName       = AnsLoginVector[i].strName;
    int iEquipmentSize   = AnsLoginVector[i].mapEquipment.size();
    int iFriendsSize     = AnsLoginVector[i].vtGuildFriend.size();
   }
  }
  else
  {
   // An error occurred.
   e.message();
   OnClose();      
  }
 }

 

귀찮습니다, 그냥 Watch창으로 확인해보죠!

 

 

 

 

넵, 그대로 날라왔습니다. type값 역시 그대롭니다. 클라이언트에도 같은 클래스로 Archive하여 그대로 나왔습니다만,

마치 마법과도 같습니다.

 

우리는 string과 map과 vector를 그대로 소켓에도 쏘고 그대로 받은 것입니다!!!!!

 

4. 직렬화가 가져온 것

지난 강좌에도 말씀드렸지만 이런 네트워크 직렬화는 결코 쉬운게 아니었습니다. 엄청난 노가다의 결실입니다. MFC의 CSocket도 물론 네트워크 직렬화를 해줍니다만, WSAAsyncSelect 모델이라 서버로 사용할만한 것은 아니었습니다.

그러나 아직까지 개선해볼 사항이 있습니다. 역시 지난 강좌에 소개한 IDL 컴파일러 같은 것이죠.

이 과정들이 자동화 될 부분이 있습니다.

 

1) 빌드시 패킷 클래스에 자동으로 직렬화 함수 넣어주기
보셨겠지만 직렬화 함수는 단순합니다. 패킷 클래스의 멤버들을 파싱하여 자동으로 직렬화 함수를 만들수 있지 않을까요?
혹은 애시당초 별도의 자신만의 스크립트 형태로 만들어 빌드가능한 클래스로 자동 생성시키는 방법이 있습니다.


2) 각 패킷별 데이터 수신 함수를 자동으로 만들어주기
1)에 연장하여 각 패킷 이름을 파싱해 수신 핸들러 함수를 만드는 것입니다.
OnAnsLogin() 식으로 만들수 있을 것입니다.

 

이는 결코 어렵지 않습니다, 힌트 다 드렸잖아요.

 

그리고 우리는 여기서 아주 중요한 아키텍트를 하나 추리해 낼수 있습니다.

게임에 사용되는 사용자 클래스를 그대로 네트워크로 내보낼 수 있으니 게임용 클래스와 네트워크 패킷을 따로 만들지 않아도 된다는 것과

독립된 서버 I/O 모듈과 게임 엔진이 잘 작동한다면 클라이언트 개발자, 혹은 서버 개발자가 아닌 컨텐츠 개발자가 혼자 게임 로직을 만들수 있게 된다는 점입니다.

이는 생산 측면에서 아주 유용합니다. 이런 모델을 추천합니다. 하지만 제가 이 컨텐츠 개발자라면 클라이언트와 서버 코드, 둘다 제것으로 만들고 공부할 것입니다. 이런 개발 모델은 편한 작업 환경에서 팀생산성을 위한 것이지 개발자에게 서로 독립된 레이어니까 전혀 몰라도 된다~라는 의미는 아니라고 생각합니다.

 

5. 더 생각해볼 꺼리

평소에 자신의 소스는 항상 너무 빨리 빌드가 되어 불만이셨던 분 계십니까?

상용 게임 빌드는 몇시간씩 걸린다는데 나는 언제 그런 빌드 타임 걸려보냐라구요?

 

그런 고민, 이번 강좌를 통해 말끔히 해결됩니다,

아마 Boost.Serialize가 추가된 순간부터 눈에 띄는 빌드 속력 저하를 느끼셨을 것입니다.

이 짧은 소스도 앗! 하는 느낌이 올껍니다!

 

갑자기 빌드가 느려진 이유를 찾아보세요.

그리고 어떻게 하면 극복할 것인지 생각해보시길 바랍니다.

 

직렬화 관련 참고자료 : http://en.highscore.de/cpp/boost/serialization.html

 

내용은 맘대로 퍼갈수 있지만 동의없는 수정은 안되며 출처(http://www.gamedevforever.com/ , http://rhea.pe.kr/)를 명시해주세요.

 

 

반응형
,