이번에는 좀 대중적인 꽁수 하나 얘기해보겠습니다.
예를 들어 오브젝트 1000 개가 씬(scene)에 있을 때, 임의의 오브젝트가 이번 프레임에 렌더링이 되었는 지를 체크 해야 한다고 생각해봅시다.
가장 쉽게 생각할 수 있는 방법은 오브젝트마다 프래그를 두어 매 프레임마다 초기화하고, 렌더링하는 오브젝트에 대해서 그 프래그를 ON 시키는 방법일 겁니다. (하지만 이 방법은 오브젝트가 많아질수록 초기화 하는 부분에 대한 부담이 생길 겁니다.)
이걸 보완하는 방법으로 많이 가장 사용하는 방법은, 글로벌하게 프레임의 카운트를 두어서 매 프레임별로 증가시키고, 오브젝트가 렌더링 될 때의 프레임 카운트를 가지게 해서 마지막 렌더링 된게 이번 프레임인지 체크하는 방법입니다.
이 경우에는 현재 프레임 카운트가 증가하게 되면 모든 오브젝트의 카운트는 현재 프레임 카운트보다 작은 값을 가지므로 초기화가 따로 필요가 없게 되겠죠.
단, 값이 오버플로우 되는 시점에서는 문제가 되므로, 그 시점에서 모든 오브젝트의 프레임을 초기화 시켜주면 됩니다. (카운터가 unsigned short 일 경우 65535에서 0으로 바뀌는 시점에, 예전부터 한번도 렌더링 된 적이 없는 오브젝트는 0을 가지고 있기 때문에 현재 프레임에 렌더링 된 거라고 판단될 수 있으므로, 이 경우는 카운터를 1로 만들도 오든 오브젝트를 0으로 초기화)
카운터가 unsigned long 형이면 40억번 정도는 카운터 초기화할 필요가 없겠네요.
특히나 Octree 로 구성된 씬(scene)에서는 겹치는 오브젝트들이 생기기 때문에, 충돌체크나 렌더링할 때 중복해서 처리 하지 않도록 해주어야 하는 데, 이 방법을 사용하면 어느 정도 이득을 볼 수 있을 겁니다. (octree 탐색할 일이 많아지면 많아질수록 도움이 되겠죠. - 충돌처리만 생각해도 꽤 많이 호출 될 거 같네요.)
그리고 단순히 ON이냐 OFF냐가 아니라 임의의 수를 할당하는 식으로 약간 확장하는 방법을 제안합니다.
예를 들어서 버텍스 100개, 페이스 100개 짜리를 인덱싱된 오브젝트를 찍는 다고 해보겠습니다.
컬링만해도 화면에 찍을 폴리곤 수는 50개 정도라고 할 수 있겠습니다. 그럼 (버텍스 100개 페이스 50개)의 인덱싱 정보를 넘기면 되겠습니다만, 실제로 버텍스 중에선 30개 정도는 안 쓰일 것입니다.
이럴 경우 단순하게 사용하는 버텍스만 체크하는 것은 아래처럼 만들 수 있겠습니다.
int idx[100]; int i, n=0, c=0, j;
for(i=0; i<100; i++) idx[i] = -1;
for(i=0; i<facen; i++) { if (ISRENDER(face[i])) { for(j=0; j<3; j++) { if (idx[face[i][j]] < 0) { newvtx[c] = vtx[face[i][j]]; idx[face[i][j]] = c; c++; }
newface[n][j] = idx[face[i][j]]; } n++; } }
|
여기서 부담이 되는 부분은 매번 idx 100개를 -1 로 초기화하는 부분일 겁니다. (물론 100개야 애교지만.. ^^)
여기서 아까의 방식을 약간 응용해보면...
static unsigned long idx[100]; static unsigned long cnt = 0;
int i, n=0, c=0, j;
if (cnt == 0 || ((unsigned long)cnt + 100 < cnt)) // cnt + 100 < cnt -> 오버플로우체크 { for(i=0; i<100; i++) idx[i] = 0; cnt = 100; } else cnt += 100;
for(i=0; i<facen; i++) { if (ISRENDER(face[i])) { for(j=0; j<3; j++) { if (idx[face[i][j]] < cnt) { newvtx[(c-cnt)] = vtx[face[i][j]]; idx[face[i][j]] = c + cnt; c++; }
newface[n][j] = idx[face[i][j]] - cnt; } n++; } }
|
가 되겠습니다. 이 경우에는 4천만번정도는 초기화하지 않아도 되겠네요. (idx 개수를 늘려서 4000개로 잡아도 백 만번 정도는 클리어 없이 사용해도 되겠습니다.)
여기서 하나만 더 응용해보겠습니다.
경우에 따라선 오버플로우 되서 초기화되는 부분이 부담스러울 수도 있습니다. (그럴 일이야 거의 없지만 순간적으로 프레임이 끊길 수도 있겠죠.) 이럴 경우를 커버할 방법을 생각해보겠습니다.
문제 되는 부분은 한번에 클리어 할 내용이 많다는 거겠죠. 그래서 한번에 클리어 하지 말고, 매번 조금씩 나누어서 클리어 하는 식으로 구현하면 될 거 같습니다.
그래서 아래처럼 구현해봤습니다.
static unsigned short idx[100]; static unsigned short cnt = 0; static int clearnum = 0;
int i, n=0, c=0, line;
if (cnt == 0 || (unsigned short)(cnt+100) < cnt) { for(i=clearnum; i<100; i++) idx[i] = cnt;
clearnum = 0; cnt = 100; } else { line = (100 * cnt) >> (sizeof(*idx)*8); while(clearnum < line) idx[clearnum++] = cnt; }
cnt += 100;
for(i=0; i<facen; i++) { if (ISRENDER(face[i])) { for(j=0; j<3; j++) { if ((unsigned long)(idx[i]-cnt) >= 100) { newvtx[(c-cnt)] = vtx[face[i][j]]; idx[face[i][j]] = c + cnt; c++; }
newface[n][j] = idx[face[i][j]] - cnt; } n++; } }
|
: 메모리를 절약(?)하는 차원에서 unsigned short 으로 사용했습니다.
: (unsigned long)(idx[i]-cnt) >= 100 이부분은 (idx[i]-cnt < 0 || idx[i]-cnt >= 100) 와 같은 의미입니다.
이 경우에는 650번 정도 반복하면 카운터가 오버플로우 되어 초기화 될겁니다. (범위는 0에서 65535이고 100씩 증가하므로...)
위의 루틴이면 650번동안 100바이트를 나누어서 클리어하게 되고, 약 6에서 7번에 한번씩 1바이트씩 클리어하게 됩니다. (이 정도면 클리어로 무리가 생기는 경우는 없겠죠 ?)
unsigned long 으로 잡으면 42만번에 한 개씩 클리어 하겠네요. (이 경우 (100 * cnt) >> (sizeof(*idx)*8) <- 이 부분은 다시 작성하셔야 할겁니다. 100*cnt 가 오버플로우 될 수 있으므로...)
단, 이런 식으로 처리하게 되면 불필요하게 복잡해질 수 있으니, 적당히 판단해서 적용하시는 것이.... ^^ (코딩 & 디버깅시간 대 효율비로 판단해서...)
아래는 제가 클래스로 만들어서 사용하는 루틴입니다. 참고하세요. IsUpdated 로 갱신 되었는 지 체크하고, GetValue, Update 로 갱신해서 사용합니다. 갱신 빈도에 따라서 1바이트나 2바이트로 사용하려고 템플릿 클래스로 만들었습니다. 사용하는 데, 특별히 문제된 적은 없습니다만, 버그가 없는 지는 확신할 수는 없네요.
template <class _T> class UpdateCounter { public : UpdateCounter(int n, int range=1) { m_cnt = new _T [n]; m_cnt_num = n;
m_mask = range - 1; m_current = 0;
m_step = m_cnt_num / ((1<<(8*sizeof(_T))) / range - 1) + 1; m_clear = 0;
memset(m_cnt, 0, sizeof(_T) * m_cnt_num);
Clear(); }
~UpdateCounter() { delete [] m_cnt; }
void Clear() { ClearBlock();
m_current += (m_mask+1); }
void Update(int cnt, int v=0) { m_cnt[cnt] = m_current + v; }
int GetValue(int cnt) { return m_cnt[cnt] - m_current; }
bool IsUpdated(int cnt) { return (_T)(m_cnt[cnt] - m_current) <= m_mask ? true : false; }
private : void ClearBlock() { int len = m_cnt_num - m_clear > m_step ? m_step : m_cnt_num - m_clear, i;
if (len > 0) { m_cnt[m_clear] = m_current;
for(i=1; i<len; i+=i) memcpy(&m_cnt[m_clear+i], &m_cnt[m_clear], (len-i > i ? i : len-i) * sizeof(*m_cnt)); }
m_clear = m_clear + len >= m_cnt_num ? 0 : m_clear + len; }
_T * m_cnt, m_current, m_mask; int m_cnt_num, m_step, m_clear; } ;
|
댓글을 달아 주세요
너는 아름다운 웹사이트가 있는다!