svn 파일을 mac에서 vi로 확인을 해보니 파일이 죄다 깨져있고 심지어 파일 자체를 binary로 인식하고 있었다.


file -I 파일 이름으로 확인해보니


text/x-c++; charset=utf-16le << 로 출력이 되어


이것을 찾아보다가 Encode명령어로 변경하여 해결했다.


iconv 명령어가 바로 그것인데


iconv -f utf-16le -t us-ascii 파일이름 > 새로운파일이름


얼라?


cannot convert  error를 토해내고 있다....


이런 경우에 먼저 utf-16으로 변경해준다음에 다시 us-ascii 로 변경하니 해결 됐다.


iconv -f utf-16le -t utf-16 test.cs > test1.cs

iconv -f utf-16 -t us-ascii test1.cs > test2.cs

cp -fv test2.cs test.cs


로 다시 엎어주면 해결됨.



'프로그래밍 > UNIX & LINUX' 카테고리의 다른 글

[명령어] grep  (0) 2016.11.24

grep 명령어는 파일의 텍스트 패턴을 찾을 때 사용하는 강력한 프로그램입니다. ( 너무 강력해서 좀 무섭다.... )


grep은 파일 내에서 "패턴"을 만났을 때, 그 패턴을 가지고 있는 라인을 출력 합니다. grep은 다른 명령어랑 혼합해서 쓰는 것이 가능하며, 정규 표현식 이라고 하는 고급수준의 패턴을 사용하여 더 정밀한 검색 또한 가능합니다.


기본적으로  grep 명령어 혼자일 때 보다는 혼합해서 사용했을 때 더욱 효과를 발휘 합니다.


사용법은 다음과 같습니다.


grep [옵션] [패턴] [파일 또는 디렉토리]


옵션 종류

-A num : 일치하는 줄 다음에 지정한 줄 수(num)만큼의 내용을 더 보여준다.

-b : 일치하는 줄을 출력하는 내용의 맨 앞에 바이트 오프셋(byte offset)을 보여준다. ( 패턴 매칭되기전 라인의 바이트수를 출력 )

-B num : 일치하는 줄의 위에 지정한 줄 수만큼의 내용을 더 보여준다.

-c : 일치하는 줄의 수를 보여준다.

-C : 일치하는 줄의 위와 아래에 지정한 줄 수만큼의 내용을 더 보여준다.

-d [action] : 읽고자 하는 파일이 디렉터리 일 경우 지정한 값을 실행한다. 기본값으로는 read이다. 아래는 실행 가능한 값이다.

read : 디렉토리를 보통 파일처럼 읽는다.

skip : 디렉토리를 건너뛴다.

recurse : 디렉토리를 포함하여 하위 디렉터리의 모든 파일을 읽는다.


-e [패턴] : 하나 이상의 탐색 패턴을 지정한다. 단순한 패턴으로 동작하지만 패턴이 -로 시작할 때 유용하다.

-E : 확장 정규표현식으로 패턴을 해석한다.( = egrep 같은 의미)

-f [파일] : 패턴을 지정한 파일에서 가져온다.

-h : 패턴의 결과 목록만 보여주고 지정한 파일명은 출력하지 않는다.

-i : 대소문자의 구별을 하지 않는다.

-l : 일치하는 줄의 파일명만 보여주고, 줄의 내용은 출력하지 않는다.

-n : 일치하는 줄의 내용과 해당 줄의 위치를 출력한다.

-q : 결과를 출력하지 않는다.

-r : 각 디렉토리의 하위에 존재하는 파일들을 읽는다. -d recurse 옵션과 동일한다.

-s : 존재하지 않거나 읽을 수 없는 파일의 결과로 에러를 출력하지 않는다.

-v : 지정한 패턴과 일치하지 않는 내용을 보여준다.

-w : 지정한 패턴과 워드 단위로 일치하는 결과만을 보여준다.

-x : 패턴과 일치하는 전체 줄 수를 보여준다.

-y : -i 옵션과 동일한다.


< grep에서 사용하는 정규 표현식 메타 문자 >

메타문자
기    능
사용 예
사용 예 설명
^
행의 시작 지시자
'^love'
love로 시작하는 모든 행과 대응
$
행의 끝 지시자
'love$'
love로 끝나는 모든 행과 대응
.
하나의 문자와 대응
'l..e'
l 다음에 두 글자가 나오고 e로 끝나는 문자열을 포함하는 행과 대응
*
선행문자와 같은 문자의 0개 혹은 임의개수와 대응
*love'
0개 혹은 임의 개수의 공백 문자 후에 love로 끝나는 문자열을 포함한 행과 대응(*앞에 공백이 있는 상태, 다른 문자가 있다면 그 문자가 0개 혹은 임의 개수의 문자 후에 love로 끝나느 경우를 말함)
[]
[] 사이의 문자 집합중 하나와 대응
'[Ll]ove'
love나 Love를 포함하는 행과 대응
[^ ]
문자집합에 속하지 않는 한 문자와 대응
'[^A-K]love'
A와 K 사이의 범위에 포함되지 않는 한 문자와 ove가 붙어있는 문자열과 대응
\<
단어의 시작 지시자
'\<love'
love로 시작하는 단어를 포함하는 행과 대응(vi,grep에서 지원)
\>
단어의 끝 지시자
'love\>'
love로 끝나는 단어를 포함하는 행과 대응
(vi,grep에서 지원)
\(..\)
다음 사용을 위해 태그를 붙인다.
'\(lov\)ing'
지정된 부분을 태크1에 저장한다. 나중에 태그값을 참고하려면 \1을 쓴다. 맨 왼쪽부터 시작해 태그를 9개가지 쓸 수 있다. 왼쪽 예에서는 lov가 레지스터1에 저장되고 나중에 \1로 참고할 수 있다.
x\{m\}
문자 x를 m번 반복한다.
'o\{5\}'
문자 o가 5회 연속적으로 나오는 모든 행과 대응
x\{m,\}
적어도 m번 반복한다.
'o\{5,\}'
문자 o가 최소한 5회 반복되는 모든 행과 대응
x\{m,n\}
m회 이상 n회 이하 반복한다.
o\{5,10\}'
문자 o가 5회에서 10회 사이의 횟수로 연속적으로 나타나는 문자열과 대응
(표참조 : http://www.leopit.com/Leophp/board/lecture_board/view.php?id=61&board_mode=linux)


옵션의 예를 하나씩 보도록 하겠습니다.







'프로그래밍 > UNIX & LINUX' 카테고리의 다른 글

iconv 사용 후기  (0) 2017.02.27

Jenkins 및 기타 등등으로 자동 빌드를 하기 위해서 필수적으로 들어가는 Command Line

Unity에서도 Command Line을 지원해주고 있습니다.


근데 이걸 어느 컬럼에 써야 될지 난감하긴 하네요.... 주된 내용이 C#이니 그리고 활용은 Unity뿐만 아니라 Command Line에서 사용 가능하니 C# 컬럼에서 작성하겠습니다.


기본적으로 Unity에서 Command Line을 지원하는데 그에 대한 정보는 

https://docs.unity3d.com/kr/current/Manual/CommandLineArguments.html 에 참조하시면 됩니다.


Jenkins에서는 기본적으로 plugin을 제공해주고 있고, 이부분은 다음에 설명을 하도록 하고 대충 사용 방법을 설명하면 이렇게 사용합니다.

 - Mac 기준

{유니티설치경로}/Unity.app/Contents/MacOS/Unity -quit -batchMode - buildTarget Android -projectPath 프로젝트경로 -excuteMethod 클래스명.함수명


프로젝트 경로가 Users/Dosinamja/project 이고 사용하는 함수가 AutoBuilder.BuildAndroid라고 했을 경우에는 다음과 같습니다.


/Applications/Unity/Unity.app/Contents/MacOS/Unity -quit -batchMode -buildTarget Android -projectPath Users/Dosinamja/project -excuteMethod AutoBuilder.BuildAndroid


대충 이런식으로 사용됩니다.


이렇게 하면 Command Line 으로 빌드를 수행할수 있긴 하지만 사용하다가 보면 명령인자를 넘겨줘서 처리하고 싶을 경우가 많이 생깁니다. 그렇다고 매번 소스를 수정한 뒤에 빌드를 수행하기에는 매우 번거롭고 생산적이지 못하지요.


그래서 실질적으로 arguments 를 넘겨줘서 원하는 작업을 수행하기 위해서 작업이 필요합니다.

기본적으로 명령인자를  parsing 하여 우리가 원하는 값들을 뽑아내서 사용한다고 생각하면 된됩니다.


기본적인 코드는 GitHub에 올라와 있습니다.( 물론 제가 만든것은 아닙니다. )

https://github.com/EpixCode/CommandLineCustomArguments/blob/master/Assets/Scripts/Com/EpixCode/Util/CommandLineReader.cs#L76


#region Using

using System;

using System.Collections.Generic;

using System.Linq;

using UnityEngine;

#endregion


public class CommandLineReader

{

    //Config

    private const string CUSTOM_ARGS_PREFIX = "-CustomArgs:";

    private const char CUSTOM_ARGS_SEPARATOR = ';';


    public static string[] GetCommandLineArgs()

    {

        return Environment.GetCommandLineArgs();

    }


    public static string GetCommandLine()

    {

        string[] args = GetCommandLineArgs();

        if (args.Length > 0)

        {

            return string.Join(" ", args);

        }

        else

        {

            Debug.LogError("CommandLineReader.cs - GetCommandLine() - Can't find any command line arguments!");

            return "";

        }

    }


    public static Dictionary<string,string> GetCustomArguments()

    {

        Dictionary<string, string> customArgsDict = new Dictionary<string, string>();

        string[] commandLineArgs = GetCommandLineArgs();

        string[] customArgs;

        string[] customArgBuffer;

        string customArgsStr = "";


        try

        {

            customArgsStr = commandLineArgs.Where(row => row.Contains(CUSTOM_ARGS_PREFIX)).Single();

        }

        catch (Exception e)

        {

            Debug.LogError("CommandLineReader.cs - GetCustomArguments() - Can't retrieve any custom arguments in the command line [" + commandLineArgs + "]. Exception: " + e);

            return customArgsDict;

        }


        customArgsStr = customArgsStr.Replace(CUSTOM_ARGS_PREFIX, "");

        customArgs = customArgsStr.Split(CUSTOM_ARGS_SEPARATOR);


        foreach (string customArg in customArgs)

        {

            customArgBuffer = customArg.Split('=');

            if (customArgBuffer.Length == 2)

            {

                customArgsDict.Add(customArgBuffer[0], customArgBuffer[1]);

            }

            else

            {

                Debug.LogWarning("CommandLineReader.cs - GetCustomArguments() - The custom argument [" + customArg + "] seem to be malformed.");

            }

        }


        return customArgsDict;

    }


    public static string GetCustomArgument(string argumentName)

    {

        Dictionary<string, string> customArgsDict = GetCustomArguments();


        if (customArgsDict.ContainsKey(argumentName))

        {

            return customArgsDict[argumentName];

        }

        else

        {

            Debug.LogError("CommandLineReader.cs - GetCustomArgument() - Can't retrieve any custom argument named [" + argumentName + "] in the command line [" + GetCommandLine() + "].");

            return "";

        }

    }

}



사용방법은 다음과 같습니다.



/Applications/Unity/Unity.app/Contents/MacOS/Unity -quit -batchMode -buildTarget Android -projectPath Users/Dosinamja/project -excuteMethod AutoBuilder.BuildAndroid -CustomArgs:Language=en_US;Version=1.02


이런식으로 인자를 붙여서 CommandLine 을 사용하면 됩니다.


.cs파일에서는 


public class AutoBuilder

{

static void BuildAndroid

{

string language = string.Empty;

language = CommandLineReader.GetCustomArgument("Language");

if( String.IsNullOrEmpty(language) == false && language == "en_US" )

{

// 해당 코드 수행.

}


string version = string.Empty;

version = CommandLineReader.GetCustomArgument("Version");

if( String.IsNullOrEmpty(version) == false && version == "1.02" )

{

// 해당 코드 수행.

}

}

}


이런식으로 작업하면 됩니다.

또 다른 방법으로는 매번 CustomArgs: 를 설정하기 싫으시면 BuildAndroid()함수를 호출하기 전에 명령인자를 죄다 저장한뒤에 특정 문자열 키를 저장하고 그 해당 키값을 가져쓰는 방식으로 사용하면 됩니다.


static void BuildAndroid

{

InitCommandLineArgs();

}


static private void InitCommandLineArgs()

{

string[] commandArgs;

commandArgs = Environment.GetCommandLineArgs();


for( int i = 0; i < commandArgs.Length; ++i )

{

// 명령인자 매개변수 앞에 특정 문자열을 파싱해서

// 해당하는 매개변수 명을 키값으로 하는 Dictionary 설정.

// 나중에 Dictionary에서 특정 키값으로 하는 값을 가져옴.

}

}


이렇게 사용하면 가능합니다. CommandLine을 좀 더 유연하게 사용하여 원하시는 작업 하시길 바랍니다.

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

partial classes  (0) 2016.11.21
JsonFx 파일 가독성 높게 저장하기  (0) 2016.11.21

원문 http://cafe.naver.com/graphicsprogramming/6


근데 사이트가 없어짐....


0. 들어가며

 

Shader X2 를 살펴 보기 이전에 쉐이더가 무엇인지에 대해서 살펴 보고 가야 할 것 같습니다.

 

물론 책을 보는 것이 더 빠를 수도 있겠지만 저같은 허접이 한 번 정리해 보는 것도 쉽게 접근할 수 있는 방법이 되지 않을까 생각합니다.

 

1. Fixed Pipeline

 

보통 게임 프로그래밍을 한다고 하면 C/C++ 에 대해서 공부하고, Win32 나 MFC 응용프로그램을 작성하고, 그리고 나서 OpenGL 이나 Direct3D 같은 라이브러리를 응용프로그램에 통합하여 사용하게 됩니다.

 

Direct3D 튜토리얼이나 입문 서적들을 보면서 장치, 정점 버퍼, 인덱스 버퍼, 텍스처, 변환, 광원, 재질 등에 대해서 공부하게 됩니다. 제일 간단한 형태가 아래와 같은 구현이겠죠.

 

    장치 생성

    정점 버퍼 생성

    인덱스 버퍼 생성

    텍스처 생성

 

    Clear()

    BeginScene()

        각종 렌더 스테이트 설정

        정점 스트림 설정

        인덱스 스트림 설정

        광원 설정

        재질 설정

        텍스처 설정

        각종 텍스처 스테이지 스테이트 설정

        각종 샘플러 스테이트 설정

        텍스처 설정

        뷰행렬 설정

        투영행렬 설정

        월드 행렬 설정

        DrawXXX()

    EndScene()

    Present()

 

    텍스처 언로드

    인덱스 버퍼 언로드

    정점 버퍼 언로드

    장치 언로드

 

뭐 빠진 부분도 있겠지만 BeginScene() 과 EndScene() 사이에 여러 가지 상태를 설정하고 Draw() 한 다음에 장치에 제출(Present)하게 됩니다. 이러한 일련의 과정들을 수행하기 위해서 Direct3D9 는 여러 가지 함수들을 제공합니다.

 

하지만 자세히 살펴 보면 이러한 여러 가지 작업들은 자료(Data) 와 연산(Operation) 으로 구분할 수 있다는 것을 알 수 있습니다.

 

SetXXX() 를 통해 설정하는 광원, 재질, 텍스처, 정점, 인덱스, 행렬 등은 응용프로그램에서 준비한 자료라 할 수 있고, 각종 SetXXXState() 메서드는 실제 그래픽 카드 내에서의 연산 방식을 설정하는 것이라 할 수 있습니다. 물론 이 연산에는 조건이라는 부분도 포함이 됩니다.

 

예를 들어 하나의 메시(Mesh) 를 렌더링한다고 합시다. 메시는 Max 등의 모델링 툴을 통해서 생성되거나 프로그래머가 직접 정점 위치를 설정하여 만들어 낼 수 있습니다. 보통 메시 자체의 공간인 오브젝트 공간을 가지고 있으며, 이를 3D 월드 내에서 표현하기 위해서는 월드 변환을 수행해 주어야 합니다. 그 다음으로는 보이지 않는 데이터들에 대한 처리 비용을 줄이기 위해서 뷰 공간으로 이동하고, 최종적으로 화면에 투영하기 위해서 절단 공간으로 이동하게 됩니다.

 

이러한 과정들을 처리하기 위해서 우리는 아래와 같이 간단하게 행렬들을 설정합니다.

 

    m_pD3DDevice->SetTransform( D3DTS_WORLD, &mWorld );      // 월드 행렬

    m_pD3DDevice->SetTransform( D3DTS_VIEW, &mView );          // 뷰 행렬

    m_pD3DDevice->SetTransform( D3DTS_PROJECTION, &mProj); // 투영 행렬

 

내부적으로는 어떤 식으로 처리되든지 우리는 개념적으로 파악하고 있기만 하면 되고 모든 계산은 라이브러리 및 GPU 가 알아서 해 줍니다.

 

안개를 설정할 때도 마찬가지로 자료와 연산의 종류만 설정해 주면 됩니다. 안개 이론이 어쩌고 저쩌고 알지 못해도 간단하게 몇 줄 호출함으로써 이를 구현하는 것이 가능합니다.

 

    float Start = 0.5f,
          End   = 0.8f;
 
    g_pDevice->SetRenderState(D3DRS_FOGENABLE, TRUE);
 
    g_pDevice->SetRenderState(D3DRS_FOGCOLOR, Color);
    
    g_pDevice->SetRenderState(D3DRS_FOGVERTEXMODE, D3DFOG_LINEAR);
    g_pDevice->SetRenderState(D3DRS_FOGSTART, *(DWORD *)(&Start));
    g_pDevice->SetRenderState(D3DRS_FOGEND,   *(DWORD *)(&End));

 

D3DRS_FOGENABLE 에서 안개를 렌더링하라고 조건을 설정한 것이며,

D3DRS_FOGCOLOR 는 안개 색상을 설정하고,

D3DRS_FOGVERTEXMODE 는 안개 연산을 위한 알고리즘을 설정하고,

D3DRS_FOGSTART 와 D3DRS_FOGEND 는 안개의 시작 및 종료를 설정합니다.

 

매우 간단하지 않습니까. 안개를 렌더링하는 알고리즘은 D3DFOG_EXP, D3DFOG_EXP2 등으로 간단히 대체될 수도 있습니다.

 

우리는 단순하게 FOG 를 사용할 것임을 설정하고 관련 자료 및 연산을 "미리 정해진 형식"으로 설정하기만 하면 됩니다.

 

이렇게 "미리" 정해진 연산을 수행할 수 있도록 제공되는 기능이 바로 고정함수 파이프라인(Fixed Pipeline)입니다.

 

2. Shader, Programmable Pipeline

 

고정함수 파이프라인을 이용해서 왠만한 것들을 표현하는 것이 가능합니다. 하지만 시간이 지날 수록 사람들은 고정함수 파이프라인이 제공하지 않는 다른 특별한 표현을 하고자 하는 욕망을 품게 되었으며, 고정 함수 파이프라인의 성능에 대한 의구심을 품게 되었습니다.

 

예를 들면 "D3DFOG_LINEAR, D3DFOG_EXP, D3DFOG_EXP2 가 아니라 Volume Fog 를 사용하고 싶어요" 같은 요구가 생겨나게 된 것입니다. D3D 가 고정적으로 지원하는 기능이 아니기 때문에 사용자가 직접 구현해야 할 필요가 생긴 것입니다.

 

이러한 요구를 받아 들여 하드웨어 제조 업체와 마이크로 소프트는 Vertex Shader 와 Pixel Shader 라는 것을 발표하게 됩니다. 이 쉐이더를 사용하는 일련의 작업들은 하드웨어 상에서 처리되는 작업들에 대한 코드를 프로그래머가 직접 조작할 수 있다는 의미에서 프로그래밍 가능한 파이프라인(programmable pipeline) 이라고 불립니다.

 

Vertex Shader 는 정점 처리에 대한 부분이고 Pixel 쉐이더는 픽셀 처리에 대한 부분입니다. Pixel 쉐이더는 fragment 쉐이더라고 불리기도 하는데, 개인적으로는 프래그먼트 쉐이더가 더 정확한 의미를 전달한다고 생각합니다. 일반적으로 픽셀이라는 것은 화면에 찍히는 점 하나를 의미합니다. 하지만 프래그먼트는 그 점이 계산하기 위해서 필요로 하는 여러 가지 정보들을 모두 포함하는 개념입니다.

 

여기에서 우리가 눈치챌 수 있는 사실은 어떤 자료를 가지고 작업을 하느냐에 따라서 XXX Shader 라는 이름이 붙는다는 것입니다. 물론 자료에 따라서 수행할 수 있는 연산들은 다르겠죠. 이 시점에서 우리는 Direct3D 아키텍처를 살펴볼 필요가 있습니다.

 

 
Vertex Data 와 Primitive Data 는 Tessellation 을 거쳐, Vertex processing, Geometry Processing, Pixel Processing, Pixel Rendering 의 과정을 밟습니다. 이것을 보통 그래픽 파이프라인이라고 부르는 거죠.
 
만약 우리가 Tessellation 에 개입할 수 있다면 ( 프로그램 코드를 작성하여 원하는 대로 할 수 있다는 의미입니다 ), Tessellation Shader 라고 부르겠죠.
 
Direct3D 가 Vertex Shader 와 Pixel Shader 를 제공한다는 것은 위의 그림에서 Vertex Processing 과 Pixel Processing 부분에 대한 실행 코드를 프로그래머가 작성할 수 있게 한다는 의미입니다.
 
현재 Direct3D 9 에서는 Vertex Shader 와 Pixel Shader 만을 지원하고 있지만 Direct3D 10 부터는 Geometry Shader 와 Rasterize Shader 를 지원한다고 합니다. 즉 하드웨어 상에서 Primitive 단위로 연산을 수행할 수 있다는 의미입니다. 아직 쉐이더에 익숙하지 않으신 분들은 그것이 어떤 의미를 가지는 지에 대해 잘 못 느끼시겠지만, 간단한 예를 들면 "마우스가 올라간 삼각형을 빨갛게 만들어 주세요" 라는 식의 작업을 Geometry Shader 에서 할 수 있다는 것입니다.
 
아래 그림은 Direct3D 10 의 파이프라인 개념도입니다.
 
 
그림만 보고 쉽게 이해하기는 어렵겠지만 VS, GS, RS, PS, 이렇게 네 개의 쉐이더를 지원한다는 것만 알고 계시면 될 것 같습니다. Direct3D9 의 architecture 와는 약간 다른 형태를 띠고 있기는 하지만 큰 부분에서의 차이는 없기 때문에 Direct3D9 architecture 및 쉐이더에 대해서 이해하는 것은 중요하다고 할 수 있습니다.


'프로그래밍 > 3D Programming' 카테고리의 다른 글

[3D수학] 백터 내적의 활용  (0) 2017.12.04
[3D수학] 벡터의 내적  (1) 2017.11.24
[3D수학] 벡터의 덧셈과 뺄셈  (0) 2017.11.09
< Culling의 종류 >  (0) 2016.11.21

원문 http://cafe.naver.com/newchany/86


1절. endian 에 대한 기초지식

아마 네트웍 프로그래밍을 조금 해보았다면, Little-Endian, Big-Endian 이라는 말을 들어 보았을것이다. 때로 order byte, byte order 라고 말하기도 하며, 작은 끝돌이, 큰 끝돌이(--;) 라고 말하기도 한다. 작은 끝돌이, 큰 끝돌이는 김치하 교수의 번역서에서 볼수 있는 단어들이다.

결론부터 말하자면 엔디안 이란 컴퓨터에서 데이타가 저장되는 순서를 말하는 것이다. 컴퓨터에서 데이타 저장은 여러분도 잘 아시다 시피 byte 단위로 저장이 된다. 그런데 이 단위 저장을 할때 각 제조업체(CPU)에 따라서 저장이 되는 순서가 서로 다르다. 예를들어 우리가 자주 사용하는 32bit 정수의 경우 한번에 4byte 의 데이타가 저장이 되는데, 이때 가장 낮은 바이트부터 저장을 하는 방식이 있고, 가장 높은 바이트부터 저장을 하는 방식이 존재한다. 전자를 Little Endian 이라고 하며, 후자를 Big Endian 이라고 한다.


1.1절. 자세한 데이타 방식

즉 Little Endian 저장방식이 적용될경우 다음과 같이 저장되게 된다.

    I : 32 bit int 형정수


    |   1byte   |
    +-----------+-----------+-----------+-----------+
    |    I1     |    I2     |    I3     |    I4     |   
    +-----------+-----------+-----------+-----------+

 addr A   addr A+1      addr A+2     addr A+3
			
반면 Big Endian 은 다음과 같이 데이타가 저장될 것이다.
    I : 32 bit int 형정수


    |   1byte   |
    +-----------+-----------+-----------+-----------+
    |    I4     |    I3     |    I2     |    I1     |   
    +-----------+-----------+-----------+-----------+

 addr A   addr A+1      addr A+2     addr A+3
			
보면 알겠지만 서로 반대되는 순서로 데이타가 저장이 됨을 알수 있을것이다.

그럼 좀더 실제적인 예를들어 보도록 하겠다. Little Endian 을 적용하는 가장 대표적인 CPU는 Intel 계열 CPU 이며, Big Endian 을 적용하는 가장 대표적인 CPU 는 Sparc 계열 CPU 이다.

우리는 Endian 의 테스트를 위해서 간단한 쏘쓰를 하나 제작할 것이다.

예제 : endian.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    int fd;
    int data=123456789;
    char c[4];
    fd = open("test_bin", O_CREAT|O_WRONLY);

    write(fd, (void *)&data, sizeof(int));
    memcpy(c, (void *)&data, sizeof(int));

    close(fd);
}
			
이 프로그램은 test_bin 이라는 이름의 파일을 open 한다음 int 형 정수를 쓰는 일을한다. 우리는 위의 프로그램을 Intel Linux 와, Sparc Solaris 에서 각각 실행시켜서 그 결과를 알아볼것이다. Sparck Solaris 에서 위의 프로그램을 컴파일,실행한후 생성되는 test_bin 파일을 ftp 등을 통해서 리눅스로 가져와서 테스트한다(동일한 환경에서 테스트하는게 혼동을 피할수 있음으로).

Linux 에서 위의 프로그램을 실행후 만들어진 파일을 test_bin_linux 로 이름을 바꾸고, Solaris 에서 만들어진 파일은 test_bin_solaris 로 이름을 바꾸고 나서 od(1) 프로그램을 이용해서 그값을 확인해 보았다.

[root@coco endian]# od -x test_bin_linux 
0000000 cd15 075b
0000004
[root@coco endian]# od -x test_bin_solaris 
0000000 5b07 15cd
			
위의 결과를 보면 Little Endian 과 Big Endian 의 차이점을 쉽게 이해할수 있을것이다. 저장방식이 바이트 단위로 서로 전혀 반대임을 알수 있다.


2절. Endian 이 실제 프로그래밍 환경에서 중요한가

Endian 에 의한 byte order 은 해당 시스템의 CPU 에서 신경을 쓰므로, 단지 하나의 시스템에서만 프로그래밍 작업을 한다면 Endian 에 대해서 전혀 신경 쓸필요가 없다.

그러나 네트웍프로그래밍을 할경우 이기종간의 (예를 들어 Sparc 과 Intel) 통신을 염두에 두어야만한다. 이럴경우 Endian 에 신경을 써주지 않으면 전혀 엉뚱한 결과를 가지고 오게 된다.


2.1절. 그럼 네트웍 상에서 어떤 문제가 발생하는가

이번에는 서버 클라이언트 프로그램을 만들어 보도록 하자. 서버 프로그램은 Sparc 솔라리스에, 클라이언트 프로그램은 Inten Linux 에서 각각 작동하도록 할것이다.

다음은 서버 프로그램이다.

server.c

	
#include <sys/time.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    int server_sockfd, client_sockfd, sockfd;
    struct sockaddr_in clientaddr, serveraddr;

    int fd_num;

    int state, client_len;
    int i, maxi, maxfd;
    int data;

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sockfd == -1)
    {
        perror("socket error : ");
        exit(0);
    }

    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(atoi(argv[1]));

    if(bind (server_sockfd, (struct sockaddr
             *)&serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("bind error ");
        exit(0);
    }

    if (listen(server_sockfd, 5) < 0)
    {
        perror("listen error : ");
        exit(0);
    }

    client_sockfd = accept(server_sockfd, (struct
                    sockaddr *)&clientaddr, &client_len);
    read(client_sockfd, (void *)&data, sizeof(int));
    printf("%d\n", data);
    close(client_sockfd);
}
			

다음은 클라이언트 프로그램이다.

client.c

#include <sys/time.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    int client_sockfd;
    struct sockaddr_in clientaddr;
    int data = 123456789;
    int client_len;

    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    clientaddr.sin_family = AF_INET;
    clientaddr.sin_addr.s_addr = inet_addr("192.168.100.190");
    clientaddr.sin_port = htons(atoi(argv[1]));

    client_len = sizeof(clientaddr);

    if (connect(client_sockfd, (struct sockaddr *)&clientaddr, client_len) < 0)
    {
        perror("Connect error : ");
        exit(0);
    }

    write(client_sockfd, (void *)&data, sizeof(int));
    close(client_sockfd);
}
			

클라이언트는 123456789 를 보내고 서버는 이값을 받아서 출력하는 단순한 일을한다. 그럼 각각의 쏘쓰를 솔라리스와 리눅스로 옮겨서 컴파일후 작동시켜보도록 하자. 서버측에서는 다음과 같은 결과를 보여줄것이다.

[root@solaris test]# ./server 12345
365779719
			
분명 클라이언트는 123456789 를 보냈는데, 서버측에서는 전혀 다른 값을 출력시킨다. 이유는 간단한다 Endian 이 달라서 데이타의 byte 가 역전되어 있기 때문이다.

그럼 이문제를 어떻게 해결해야 할까. 인테넷 상에는 그야말로 다양한 시스템이 존재하고 시스템마다 Endian 차이가 존재할건데,


2.2절. 그럼 Endian 문제의 해결방법은 ?

가장 간단한 해결방법은 Endian 이 Big 이든 Little 이든 하나로 통일시키는 거겠지만 이건 사실 불가능하다. 그럼 생각할수 있는 방법이 공통되는 Endian 으로 변환 시킨다음에 자신의 데이타를 전송하는 방법이 될것이다. 그러면 전송하는 측이나 전송받는 측이나 이 공통되는 Endian 을 알고 있을 것임으로 byte order 를 시킬수 있을것이다.

그래서 network byte order 이란것이 존재한다. 즉 network 로 데이타를 보낼때는 무조건 하나의 Endian 으로 통일을 시키는 것이다. network byte order 는 Big Endian 을 따른다. 그러므로 우리는 시스템에 관계없이 무조건 byte 를 Big Endian 에 맞도록 byte order 를 시킨다음에 네트웍을 통해서 외부로 보내면 된다. 받는 측에서는 자기에게 들어오는 Endian 이 Big 으로 통일되어 있음으로 Big Endian 을 자신의 Endian 에 맞도록 byte order 시켜주면 될것이다.

C 는 이러한 byte order 를 위한 함수를 제공한다. 함수가 하는 일은 자신의 host byte order 을 network byte order 에 맞게 변경시켜주는 것과, network byte order 을 자신의 host byte order 에 맞게 변경시켜주는 2가지 군의 함수를 제공한다.

#include <netinet/in.h>

unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);

unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
			
htonl 과 htons 는 host to network 즉 host byte order 를 network byte order 로 변경시켜주며, ntohs 는 network to host 로 그 반대로 변경 작업을 한다.

그렇다면 위의 쏘스들에서 port 지정을 위해서 왜 htons 를 사용하는지를 알수 있을것이다.

serveraddr.sin_port = htons(atoi(argv[1]));
			
자신의 host byte order 를 network byte order 로 변경 시키는 작업이다. linux 의 경우 little endian 을 따르는 byte order 를 하는데 만약 nework byte order 로 변경시키지 않는다면, 자기가 입력한것과 전혀 다른 port 가 전달될것이다. 예를 들어 349 번 포트를 htons 없이 지정했다면 34049 가 지정된것으로 전달될것이다. 결국 원하는 포트를 찾지 못하는 결과를 가져올 것이다.

이제 위의 코드를 어떻게 변경시켜야 할지 감을 잡았을 것이다. data 를 보내는 측에서는 htonl 을 이용해서 host byte order 로 변경시켜주고, 받는 측(서버) 에서는 ntohl 을 이용해서 network byte order 를 자신에게 맞도록 변경시켜주면 된다. 다음과 같이 코드를 각각 변경시키면 된다.

client.c
========
data = htonl(data);
write(client_sockfd, (void *)&data, sizeof(int));

server.c
========
printf("%d\n", ntohl(data));
close(client_sockfd);
			


2.3절. 또 다른 해결책은 없나요

byte order 함수를 사용해서 Little/Big Endian 문제의 해결은 했지만, 조금만 생각해보면 이 방법이 꽤 불편할수 있다는걸 알수 있다. 데이타를 보낼적엔 반드시 Endian 변환을 해줘야 한다. 꽤 귀찮은 작업이 될수 있다. 혹시라도 실수로 변환을 하지 않을경우에는 어떻게 될지 알수 없다.

그렇다면 byte order 에 신경쓸필요 없이 byte 단위로 데이타를 보내면 될것이다. char 를 사용하는 것이다. char 은 1byte 의 크기를 가짐으로 byte order 에 신경쓸 필요가 없이 사용가능하다. 그래서 많은 경우 네트웍 통신 프로그래밍을 할경우 char 만을 이용해서 통신을 하도록 하는 경우도 있다. 예를 들어 위의 12345678 을 int 형으로 보내는 대신 문자열로 변환시켜서 전송하는 것이다. 이럴경우에는 Endian 문제에 신경쓸필요가 없을것이다.


2.4절. Endian 체크하기

다음과 같은 간단한 코드를 이용해서 시스템의 Endian 을 체크할수 있다.

예제 : endian_check.c

int main()
{
    int i = 0x00000001;
    if( ((char *)&i)[0] )
        printf( "Littile Endian\n" );
    else
        printf( "Big Endian\n" );
}
			


3절. 결론

이상 Endian 에 대해서 간단하게 알아보았다. 보통 int 형과 같이 byte order 가 필요한 데이타 통신을 "binary 통신을 한다" 라고 말한다. 최근에는 binary 통신을 사용하지 않고 단지 text(char) 만을 이용해서 통신을 하는게 좀더 일반적인 추세이다. 그러나 어느 방식을 채택하든지 사용하기에 간편한 방식을 택하면 될것이다.


MSDN에 정의 되어 있는 내용 입니다.


It is possible to split the definition of a class or a struct, an interface or a method over two or more source files. Each source file contains a section of the type or method definition, and all parts are combined when the application is compiled.



둘 이상의 소스 파일에 대해 클래스 또는 구조체, 인터페이스 또는 메소드의 정의를 분할 할 수 있습니다. 각 소스 파일에는 형식 또는 메서드 정의의 섹션이 포함되어 있으며 응용 프로그램이 컴파일 될 때 모든 파트가 결합됩니다.


C# 2.0부터 적용되어 있는 것으로 알고 있습니다.

사용하는 이유는 여러가지가 있습니다만 제가 생각하는 이유로는
1. 프로젝트에 여러사람이 접근해서 작업을 할 경우에 소스 merge에 이점을 두거나 해당 부분을 나눠서 작업을 할 수 있는 이점이 있습니다.
 - 예를 들어서 몬스터의 AI 부분 작업을 여러사람이 나눠서 하게 된다면 AI의 해당 부분들( 플레이어 공격, 휴식, 죽음 등등 )을 각각의 파일로 나눠서 작업하게 되면 나중에 접근하기도 쉽고, 나눠서 작업하기도 편합니다. 이후에 예를 설명 드리겠습니다.

2. MSDN에서 설명되어 있기를... Visual Studio에서는 Windows Forms, 웹 서비스 래퍼 코드 등을 만들 때이 방법을 사용합니다. Visual Studio로 만든 파일을 수정하지 않고 이러한 클래스를 사용하는 코드를 만들 수 있습니다.
- 뭐 대충 자동으로 생성되는 코드랑 작업자가 사용하는 코드를 분류해서 사용하면 실수를 미연에 방지할 수 있다는 내용입니다만 전 이런거 사용을 하지 않기 때문에 1번의 이유가 더 확실하죠.

< 간단한 코드를 예를 들어보이겠습니다. >


EnemyAI.cs

public partial class EnemyAI {

    public string Init() {

        return "Enemy AI Init";

    }

}


EnemyAI_Attack.cs

public partial class EnemyAI {

    public string Attack() {

        return "Enemy Attack!!!!";

    }

}


EnemyAI_Death.cs

public partial class EnemyAI {

    public string Death() 

    {

        if( HP > 0 )

            return "No Die";


        return "Enemy Death";

    }

}


Program.cs

public class Program {

    public static void Main() {

        EnemyAI enemy_ai = new EnemyAI();

        Console.WriteLine(enemy_ai.Init());

        Console.WriteLine(enemy_ai.Attack());

        Console.WriteLine(enemy_ai.Death());

    }

}


이런식으로 파일을 나눠서 작업을 하면 좀 더 명확하게 작업을 분류할 수 있고 나중에 특정 작업에 대해서 접근 하기도 쉬워집니다. 다만 너무 많아지면 나중에 오히려 분류하기 힘들어지니 너무 많은 분류는 자제하시는게 좋을 것 같네요.

< 정리 >

partial class는 CLR 수준이 아닌 C# Compiler 수준에서 처리되므로 많은 partial class를 만들어도 실제 컴파일시에 하나의 클래스로 수집하여 인식하므로 단일 코드로 취합합니다.

소스코드 수준에서만 허용됩니다. binary 시엔 허용되지 않습니다.

무분별한 partial class 의 사용은 더욱 복잡해질 소지가 있습니다.

여러 파일에 partial 클래스로 선언시 그 클래스의 멤버들을 보는 것은 복잡하겠지만 클래스뷰를 이용하면 partial을 모아서 한 클래스처럼 보실 수 있습니다.



자세한 사항은 https://msdn.microsoft.com/en-us/library/wa80x488.aspx 참고 하시길 바랍니다.

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

Command Line Reader 클래스  (0) 2016.11.21
JsonFx 파일 가독성 높게 저장하기  (0) 2016.11.21

출처는 http://www.lameproof.com/ 요기에서 뽑아왔습니다.

 

 

컬링의 종류는 조사한 바에 의하면 3가지가 있는거 같다.

 

1. Backface culling 
2. View frustum culling 
3. Occlusion Culling

 

2번, 3번은 질의로 나온 문제였는데... 보기 좋게 제대로 못 적었다. 컄컄컄

 

Backface Culling은  object 가 closed polygon 으로 구성되어있다고 가정할때, 각 face 의 방향이 시점의 반대쪽을 바라보고 있을때에는 찍을 필요가 없다는 것을 의미한다.

 

 Frustum 은 절단체라는 것인데, 이것은 피라미드 모양에서 위의 뾰족한 부분을 잘라낸 모양을 생각하면 된다. frustum 이란 개념이 왜 나오냐 하면, 월드상에서 인간의 눈에 보이는 부분을 골라내면 바로 frustum 의 모양을 하기 때문이다. 눈에 가까운 쪽은 작은 사각형쪽에 해당하고 멀리 있는 쪽은 큰 사각형쪽에 해당한다. 물론 인간의 시야는 무한히 먼 곳을 바라볼 수 있게 되어있지만, 보통 3d 엔진에서는 시야에 어느정도 한계를 두도록 되어있다. 예를 들면 눈으로 볼 수 있는 가장 가까운 거리는 10 이고 가장 먼 거리는 5000 으로 정하는 식이다. 폴리곤이 카메라에 너무 가까와져서 모델의 내부가 보인다던가 하는 일은 거리가 최소거리보다 작은 폴리곤을 출력할때 나오는 현상인 것이다

 

  frustum 을 활용하기 위해서는 frustum 의 자료구조를 디자인하고, 실제적인 데이타 (최소거리, 최대거리, fov) 등으로부터 frustum 데이타를 만들어내는 법을 알아야 한다. frustum 은 보통 6 개의 평면이 이루는 것으로 구현하게 되어있다. 즉 near plane, far plane, left plane, right plane, top plane, bottom plane 의 6 개가 그것이다. 

  그렇다면 실질적으로 월드를 구성하는 폴리곤이 화면상에 들어오는가 안 들어오는가를 판단하기 위해서는 어떻게 하는 것일까? 그것은 각각의 폴리곤이 6 개의 plane 의 안쪽에 속하는지를 검사하는 것이다. 

  plane 은 보통 카메라좌표계에서 정의되고, 월드상의 폴리곤은 월드좌표계상에서 정의되기때문에 서로 비교를 하려면 같은 좌표계상에 오도록 변환을 해야 한다. 카메라좌표계를 기준으로 하는 것이 좋을까 월드 좌표계를 기준으로 하는 것이 좋을까? 당연히 월드좌표계를 기준으로 하는 것이 좋다. 만약 카메라 좌표계를 기준으로 한다면 폴리곤이 1 만개일 경우, 월드->>카메라 변환을 1 만번 해야 하지만, 월드좌표계를 기준으로 하면 카메라->>월드 역변환을 평면의 갯수만큼, 즉 6 번만 해주면 되기 때문이다. 평면을 변환, 역변환하는 방법에 대해선 역시나 수학책을 참고하길 바란다. 

  이제 frustum 을 구성하기도 했고, 각각의 폴리곤이 frustum 에 속하는지 비교하는 방법도 알긴 했지만 이것만으로는 부족하다. 왜냐하면 수만개의 폴리곤이 화면에 찍히는지 안찍히는지 일일이 frustum 에 대해 검사를 해야하기 때문에 실질적으로 큰 소득을 얻지 못했기 때문이다. 어떻게 해야지 간단한 방법으로 (적은 수의 계산으로) frustum 안에 들어오는 polygon 만을 골라낼 수 있을까? 

  그 해답은 polygon 을 그룹을 지워놓고 나서, 그룹단위로 frustum 에 들어오는지를 판별하는 것에 있다. 예를 들어서 월드상의 인접한 폴리곤들을 100 개정도의 그룹으로 분리(partition 을 구성)를 해 놓은 다음에 그 그룹을 최대한으로 감싸는 bounding box 를 계산해놓고, bounding box 와 frustum 간의 포함관계여부를 검사를 하면 어떻게 될까? 물론 훨씬 계산량이 줄어들 수 있게 된다. 왜냐하면 그룹의 bounding box 가 frustum 과 전혀 충돌하지 않는다는 것을 알게 되면 (적은 수의 계산으로 알 수 있다) 그 그룹안에 포함된 수백개의 폴리곤들또한 전혀 frustum 에 속하지 않는 다는 결론을 내릴 수 있기 때문이다. 

  이 방법만 쓰더라도 폴리곤 한개씩을 frustum 에 대해 비교하던 방법보다는 훨씬 나은 효과를 얻을 수 있다. 참고로 폴리곤을 일정한 단위로 그룹을 짓는다는 것은, 각각의 폴리곤이 전혀 움직이지 않는다는 가정을 했기 때문에 가능한 것이다. 만약 폴리곤이 일부라도 움직이게 되면 그룹을 다시 짜거나, 또는 그룹의 bounding box 를 재계산 해야만 할 것이다. 

  이 방법은 좋긴 하지만 더 좋게 할 수 있는 방법이 더 많이 있는데, 그중 하나는 그룹을 계층적으로 구성하는 것이다. 말하자면 월드 전체를 커다란 한개의 그룹으로 일단 구성하고, 그 그룹을 어떤 기준에 의해서 2 개나 4 개, 혹은 8 개의 소그룹으로 나누고, 그 소그룹을 다시 또 기준에 의해서 소그룹으로 분할하는 방법을 말한다. 이런식으로 일정단계까지 (예를 들면 5 단계까지) 나누고 나면 월드 전체는 그룹들로 구성된 트리구조로 표현할 수 있게 된다. 하나의 그룹을 임의의 기울기의 평면을 사용해서 2 개의 소그룹으로 나누는 방법을 Binary Space Partitioning 이라고 부르고, x 축과 z 축의 직교된 평면을 이용해 4 개의 소그룹으로 나누는 방법을 Quad-Tree Partitioning 이라고 하고, x,y,z 축의 평면을 이용해 8 개의 소그룹으로 나누는 방법을 Oct-tree partitioning 이라고 한다. bsp 와 quad-tree, oct-tree 는 각각 구조에 따라 장단점이 있는데, 실질적으로 가장 기본이 되는 것은 bsp 라고 할 수가 있고, quad-tree 나 oct-tree 는 bsp 의 단계를 묶어서 간략화한 형태라고 할 수 있다. 그리고 quad-tree 는 높낮이의 변화가 심하지 않은 2.5 차원 배경을 표현할때 효과적이고, oct-tree 는 높낮이의 변화가 심한 3 차원 배경을 표현할때 효과적이다. 

 이렇게 월드를 그룹으로 나눌때에는 몇가지 주의할 사항이 있다. bsp 건 quad-tree 건 oct-tree 건 평면을 이용해서 폴리곤들을 나누게 되어있는데, 어떤 경우건 폴리곤이 두개의 그룹간에 걸치게 되는 경우가 있다. 이런 때에는 어떻게 해야 할까? 

  첫번째 방법은 폴리곤을 2 개로 쪼개서 각각의 그룹에 하나씩 보내주는 경우이다. 이 경우에는 각각의 그룹은 폴리곤을 완벽하게 포함시키게 되지만, 폴리곤의 갯수가 늘어나게 된다는 단점이 있다. bsp 를 이용해서 은면제거까지 해야한다면 반드시 이 방법을 써야만 한다. 

  두번째 방법은 하나의 폴리곤을 양쪽에 모두 포함시키는 것이다. 이 경우에는 그룹이 폴리곤을 완전히 포함시키지 못하므로 후에 추가적인 테스트가 필요하거나 정확성이 약간 떨어지게 되고, 은면제거의 방법으로 활용할 수 없게 되지만 폴리곤의 갯수는 유지된다는 장점이 있다. 그리고 한 폴리곤이 두번 표시되지 않도록, 폴리곤을 표시했는지 여부를 각 폴리곤마다 기록해줘야 할 필요가 있다. 근래에는 보통 두번째 방법을 많이 쓰는 것으로 알고 있다. 내가 만들었던 엔진에서는 quad-tree 에 두번째 방법을 주로 이용했다. 

  실질적으로 VFC 를 이용해서 화면상에 복잡한 배경을 찍는다는 것은 3 개의 단계로 나뉘어진다 

  첫번째는 월드상의 모든 폴리곤들을 적절한 구분방법에 의해 그룹의 트리구조를 만드는 것이다. 이 구조를 보통 SceneGraph 라고 부른다. 배경(Scene) 이 어떤 구조로 나타나있는지를 표현하는 그래프라는 의미이다. bsp, quad-tree, oct-tree 는 scenegraph 의 종류를 일컫는 말이다. 이 작업은 배경이 변하지 않는 한에는 처음에 1 번만 해두면 된다. 고정된 배경이기 때문에 미리 계산을 해놓을 수 있는 것이고, 미리 계산을 해 놓기 때문에 속도를 빠르게 할 수 있는 것이다. SceneGraph 의 각 요소를 node 라고 하고, node 가 가지는 내용은, node 가 월드상에서 담당하는 범위 (bounding box), node 가 가진 폴리곤들의 리스트, node 에 딸린 자식 node 의 리스트를 나타낸다. 

  두번째는 현재 카메라의 시점을 계산해서 frustum 을 새롭게 만들어 놓는 것이다. (물론 월드 좌표계로) 이것은 시점이 바뀔때마다 매번 해야 하는 일이다. 

  세번째는 SceneGraph 의 각 노드에 대해 frustum 과의 충돌체크를 수행하고 (노드의 bounding box 와 frustum 간의 충돌체크) 만약 frustum 과 조금이라도 충돌하였으면 해당 scenegraph 의 자식 노드들에 대해 다시 각각 충돌체크를 수행하는 것이다. 만약 node 가 자식 노드가 없다면 (즉 최 말단 노드라면) 노드가 갖고 있는 폴리곤들을 화면에 그대로 찍으면 된다. 

  여기서 중요한 점은 각각의 노드들은 자식노드들을 완전히 포함하고 있기 때문에, 하나의 노드가 frustum 과 완전히 충돌하지 않는다는 것을 알게 되면, 그 자식노드들은 전혀 계산할 필요가 없다는 점이다. 이 원리에 의해서 수만개의 폴리곤들이 몇백번 이내의 계산에 의해서 훨씬 적은 수의 화면상에 들어오는 폴리곤들로 컬링되는 것이다 

Occlusion culling 을 사용할지 여부는 내가 표현하고자 하는 배경이 어떤 형태를 지니는가에 따라 달라진다. 즉, 배경이 실외중심으로 되어있느냐, 실내 중심으로 되어있느냐에 따라서 달라진다. Occlusion 이라는 것은 가리기라는 뜻인데, 완전히 불투명힌 벽같은 것 뒤에 완전히 가려진 물체는 View Frustum 상에 들어온다고 하더라도 표시할 필요가 없다는 점에 착안한 것이다. 

 그런데 실외의 경우에는 occlusion 의 발생여부를 따지기가 힘들고, 대부분 frustum 상에 들어온 물체를 모두 표현해야 하기 때문에 실외 표현 전용엔진에서는 occlusion culling 은 도입하지 않는다. 

  하지만 실내의 경우, 커다란 경우가 여러개의 방과 복도로 나뉘어져 있고, 시점이 그 내부에서 이동한다고 할때에는 상당히 많은 부분이 벽에 의해서 가려지게 된다. 이것을 활용하기 위한 방법이 occlusion culling 이다. 

  occlusion 의 체크또한 상당히 많은 량의 계산을 필요로 하게 된다. 따라서 배경이 고정되어있다는 것을 감안하여 최대한 많은 정보를 미리 계산해놓을 필요가 있는데, occlusion 정보를 미리 계산해놓은 것을 PVS (Potential Visible Set) 라고 한다. PVS 는 말 그대로 "보일 가능성이 있는 것들의 집합" 이라는 의미이다.

 

 예를 들어서 건물이 있는데, 건물을 10 개의 방과 복도로 나눌 수 있다고 생각하자. 각 방과 복도 사이에는 문(portal) 과 창이 존재하기 때문에 한 지역 안에서 다른 지역도 볼 수가 있지만, 모든 지역을 다 볼 수는 없다고 가정할 수 있다. 예를 들어서 방 1 에서는 복도를 볼 수 있고, 복도 너머의 방 2 와 방 3 을 볼 수 있다는 점을 미리 계산을 많이 해서 알아낼 수가 있다고 하면, 시점이 방 1 에 있을 때에는 먼저 pvs 정보에 입각하여 방1 (자신) 과 방2, 방3, 복도에 해당하는 폴리곤만을 VFC 할 수 있다는 의미이다. 또한 여기에 3 차원적 clipping 을 더 추가하여 portal 단위로 보이는 부분을 더욱 압축하는 방법도 생각해볼 수 있다. 이것은 portal rendering 이라고 불린다. 


 

'프로그래밍 > 3D Programming' 카테고리의 다른 글

[3D수학] 백터 내적의 활용  (0) 2017.12.04
[3D수학] 벡터의 내적  (1) 2017.11.24
[3D수학] 벡터의 덧셈과 뺄셈  (0) 2017.11.09
쉐이더란 무엇인가.  (0) 2016.11.21

기본적으로 C#에서 사용하는 Json Library는 여러종류가 있지만( 일단 알고 있는 종류는 몇개 안됨...) JsonFx에서 제대로 설정을 하지 않고 파일을 저장하면 One Line(!!!)으로 저장되는 경우가 있다. MB 단위로 파일이 저장됐는데 One Line으로 나오면... ㅠㅠ

JsonFx 에서 개행단위로 저장되는 옵션이다.


using (StreamWriter writer = File.CreateText(txtFilePath))

{

            JsonFx.Json.JsonWriter cJsonWriter = new JsonFx.Json.JsonWriter(writer);

            cJsonWriter.Settings.PrettyPrint = true;

            cJsonWriter.Write(Info);   // 저장하는 Json 포맷

}

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

Command Line Reader 클래스  (0) 2016.11.21
partial classes  (0) 2016.11.21

Unity performs many operations once per frame, including:

  • Input processing

  • Main update (including Update())

  • Main coroutine updates (including yield return null)

  • Late update (including LateUpdate())

  • Scene rendering (quite a few steps)

  • Gizmo/UI rendering

  • End of frame (including WaitForEndOfFrame)

One returns just before rendering, the other returns just after (but just before the frame is displayed). When the difference isn't important, I usually use and recommend yield return null, as it's closer to the way other Update and coroutine functions work.

+ Recent posts