프로그래밍/C++2009. 12. 10. 16:42

윈도우 프로그래밍시 콘솔프로그래밍처럼 원하는값을 찍어서 볼 수가 없다

그래서 윈도우프로그매이에서는 TRACE()를 사용한다

TRACE()는  디버깅할때 변수값 확인 또는 프로그램이 어디까지 수행되고 있는지 확인하기 위해서 사용한다

사용방법은 Trace.h 이라는 헤더파일을 만든다

Trace.h는 아래와 같이 작성한다

#include <tchar.h>

void TRACE_WIN32(LPCTSTR lpszFormat, ...)
{
 TCHAR lpszBuffer[0x160]; //버퍼 크기.
 va_list fmtList;
 va_start(fmtList, lpszFormat);
 _vstprintf_s(lpszBuffer, lpszFormat, fmtList);
 va_end(fmtList);
 ::OutputDebugString(lpszBuffer);
}


Trace.h 헤더파일을 추가한 후 아래와 같이 define 설정 후 사용한다
#define TRACE TRACE_WIN32

사용 예)

 TRACE("%d, %d\n", w, h);

내용은 출력부분에서 확인하면 된다

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

유닉스 - math 함수 포함된 소스 컴파일 하기  (0) 2011.03.18
소수점 반올림  (0) 2011.03.18
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
C/C++ 로깅 Facility  (0) 2009.04.25
Posted by 마블(이환문)
프로그래밍/C++2009. 7. 16. 15:03

STL의 컨테이너로 List라는게 있다

List는 우리가 흔히 사용하는 연결리스트 같은것 이라고 생각하면 된다

우선 간단하게 리스트에서 출력하는 방법이다

for(itor=list1.begin(); itor != list1.end(); itor++)
  cout << *itor;

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

소수점 반올림  (0) 2011.03.18
C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
파일 입출력 및 링크드 리스트  (1) 2009.05.01
C/C++ 로깅 Facility  (0) 2009.04.25
Abstract Factory Pattern  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/C++2009. 5. 1. 13:06

input.txt 파일을 읽어와서 output.csv 파일을 만드는 프로그램이다

여기에 추가적으로 input.txt 파일에 보면 각 날짜의 시간별로 온도, 습도, 강수량 등이 기록되어 있다

이를 날짜별으로 평균 온도, 최대 최소 온도, 표준편차 등을 구해서 Reprot.csv 파일로 만드는 프로그램이다

#include 
#include 
#include 
#include 

typedef struct Weather
{
	char day[11];
	char time[6];
	float temperature;
	float humidty;
	float rainfall;
	float insolation;
	float sunshine_duration;
	char windy[5];
	float wind_velocity;
	float max_wind_velocity;
	struct Weather *next;
}Weather;

typedef struct 
{
	Weather* head;
}Weather_h;

Weather_h* CreateNode(void);
void PrintList();
void CreateCSVfile(Weather_h *L, Weather *p);
void CreateReportFile(Weather *p);
float Standard_Deviation(Weather *p, Weather* nextNdoe, float avgTmp, bool end, int count);
void FreeNode(Weather_h *L, Weather *p);

void main()
{
	Weather_h* L;

	L = CreateNode();


	PrintList();

}

Weather_h* CreateNode(void)
{
	Weather_h* L;
	L = (Weather_h *)malloc(sizeof(Weather_h));
	L->head = NULL;
	return L;
}

void PrintList()
{
	Weather* p;
	Weather* newData;
	Weather_h* L;
	L = (Weather_h *)malloc(sizeof(Weather_h));
	L->head = NULL;

	FILE *fp;

	if((fp = fopen("input.txt", "r")) == NULL)
	{
		printf("File is not found!\n");
	}

	p = L->head;

	fseek(fp, 110,SEEK_SET);

	puts("=========================================================================");
	puts("계측시간 온도(℃) 습도(%) 강수량(mm) 일사량(w/m2) 일조시간(hour) 풍향 풍속(m/s) 최대풍속(m/s)");

	while(!feof(fp))
	{
		newData = (Weather *)malloc(sizeof(Weather));

		fscanf(fp, "%s %s %f %f %f %f %f %s %f %f ", newData->day, newData->time, &newData->temperature, &newData->humidty, &newData->rainfall, &newData->insolation, &newData->sunshine_duration, &newData->windy, &newData->wind_velocity, &newData->max_wind_velocity);

		if(L->head == NULL)
		{
			L->head = newData;
			p = newData;
			newData->next = NULL;

		}
		else
		{
			p->next = newData;
			newData->next = NULL;
		}

		while(p->next != NULL)
		{

			p = p->next;
		}
	}

	p = L->head;
	CreateCSVfile(L, p);
	CreateReportFile(p);
	FreeNode(L, p);
	
	puts("\ncvs 파일과 report.csv 파일을 성공적으로 만드셨습니다.");
	puts("=========================================================================");

	fclose(fp);


}



void CreateCSVfile(Weather_h* L, Weather* p)
{
	FILE *fp;
	Weather* newData;
	newData = p;

	if((fp = fopen("output.csv", "w")) == NULL)
	{
		printf("File is not found!\n");
	}

	fprintf(fp, "계측날짜, 시간,온도(℃), 습도(%), 강수량(mm), 일사량(w/m2), 일조시간(hour), 풍향, 풍속(m/s), 최대풍속(m/s) \n");
	while(newData->next != NULL)
	{
		fprintf(fp, "%s,", newData->day);
		fprintf(fp, " %s,", newData->time);
		fprintf(fp, " %.1f,", newData->temperature);
		fprintf(fp, " %.1f,", newData->humidty);
		fprintf(fp, " %.1f,", newData->rainfall);
		fprintf(fp, " %.1f,", newData->insolation);
		fprintf(fp, " %.1f,", newData->sunshine_duration);
		fprintf(fp, " %s,", newData->windy);
		fprintf(fp, " %.1f,", newData->wind_velocity);
		fprintf(fp, " %.1f\n", newData->max_wind_velocity);


		newData = newData->next;
	}

	fclose(fp);
}

void CreateReportFile(Weather* p)
{
	FILE *fp;
	Weather *nextNode, *temp, *startNode;
	float avgTem = 0;
	float sumTem = 0;
	int count=0;
	float Max = -1000;
	float Min = 1000;
	float sumHumidty = 0;
	float avgHumidty = 0;
	float sumRainfall = 0;
	float avgRainfall = 0;
	float sumSunshine_duration = 0;
	float avgSunshine_duration = 0;

	temp = p;
	startNode = p;
	nextNode = p->next;

	if((fp = fopen("Report.csv", "w")) == NULL)
	{
		printf("File is not found!\n");
	}

	fprintf(fp, "[일별기상통계표] 계측 지역:신촌 \n");
	fprintf(fp, "일자, ,기온(Temperature), , , 습도, 강수량, 일조시간\n");
	fprintf(fp, " , 평균, 표준편차, 최고, 최저, [%%], [mm], [hour]\n");


	while(p->next != NULL)
	{
		temp = startNode;

		if(strcmp(p->day, nextNode->day) == 0)
		{
			sumTem = sumTem + p->temperature;
			sumHumidty = sumHumidty + p->humidty;
			sumRainfall = sumRainfall + p->rainfall;
			sumSunshine_duration = sumSunshine_duration + p->sunshine_duration;

			count++;

			if(p->temperature > Max)
				Max = p->temperature;

			if(p->temperature < Min)
				Min = p->temperature;

			p = nextNode;
			nextNode = nextNode->next;


			if(p->next == NULL)
			{
				sumTem = sumTem + p->temperature;
				sumHumidty = sumHumidty + p->humidty;
				sumRainfall = sumRainfall + p->rainfall;
				sumSunshine_duration = sumSunshine_duration + p->sunshine_duration;
				count++;

				avgTem = sumTem / count;
				avgHumidty = sumHumidty / count;
				avgRainfall= sumRainfall / count;
				avgSunshine_duration = sumSunshine_duration / count;

				fprintf(fp, "%s, ", p->day);
				fprintf(fp, "%.1f, " , avgTem );
				fprintf(fp, "%.4f,", Standard_Deviation(temp, temp, avgTem, true, count));
				fprintf(fp, "%.1f, " , Max);
				fprintf(fp, "%.1f, " , Min);
				fprintf(fp, "%.1f, " , avgHumidty);
				fprintf(fp, "%.1f, " , avgRainfall);
				fprintf(fp, "%.1f\n " , avgSunshine_duration);

			}

		}

		else if(strcmp(p->day, nextNode->day) != 0)
		{
			sumTem = sumTem + p->temperature;
			sumHumidty = sumHumidty + p->humidty;
			sumRainfall = sumRainfall + p->rainfall;
			sumSunshine_duration = sumSunshine_duration + p->sunshine_duration;
			count++;

			avgTem = sumTem / count;
			avgHumidty = sumHumidty / count;
			avgRainfall= sumRainfall / count;
			avgSunshine_duration = sumSunshine_duration / count;

			if(p->temperature > Max)
				Max = p->temperature;

			fprintf(fp, "%s, ", p->day);
			fprintf(fp, "%.1f, " , avgTem);
			fprintf(fp, "%.4f,", Standard_Deviation(temp, nextNode, avgTem, false, count));
			fprintf(fp, "%.1f, " , Max);
			fprintf(fp, "%.1f, " , Min);
			fprintf(fp, "%.1f, " , avgHumidty);
			fprintf(fp, "%.1f, " , avgRainfall);
			fprintf(fp, "%.1f\n " , avgSunshine_duration);

			p = nextNode;
			nextNode = nextNode->next;
			startNode = p;
			avgTem = 0;
			sumTem = 0;
			sumRainfall = 0;
			sumHumidty = 0;
			sumSunshine_duration = 0;
			count = 0;
			Max = -1000;
			Min = 1000;

		}


	}



}

float Standard_Deviation(Weather* p, Weather* nextNode, float avgTmp, bool end, int count)
{
	Weather *temp = p;
	Weather *tempNext = nextNode;
	float sd = 0;
	float squaresum = 0;


	if(end == false)
	{
		while(strcmp(temp->day, tempNext->day) != 0)
		{
			squaresum = squaresum + (avgTmp - temp->temperature)*(avgTmp - temp->temperature);
			temp = temp->next;
		}
		squaresum = squaresum + (avgTmp - temp->temperature)*(avgTmp - temp->temperature);
		temp = temp->next;
	}
	else
	{
		while(temp->next != NULL)
		{
			squaresum = squaresum + (avgTmp - temp->temperature)*(avgTmp - temp->temperature);
			temp = temp->next;

		}
		squaresum = squaresum + (avgTmp - temp->temperature)*(avgTmp - temp->temperature);
		temp = temp->next;
	}
	sd = sqrt(squaresum) / count;
	return sd;	

}

void FreeNode(Weather_h *L, Weather *p)
{
	Weather *temp = L->head;


	if(L->head == NULL)
		return ;
	else
	{
		free(L);
		while(temp->next != NULL)
		{
			temp = p->next;
			free(p);
			p = temp;
		}
	}

}

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

C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
C/C++ 로깅 Facility  (0) 2009.04.25
Abstract Factory Pattern  (0) 2009.04.22
PVOID  (0) 2009.04.22
Posted by 마블(이환문)

Java RMI (Remote Method Invocation)

 

1. RMI의 정의

- RMI란 원격 메서드 호출로 네트워크상에서 떨어져 있는 객체의 메서드를 투명하게 호출하는 것을 말한다.

 

2. RMI 의 개발 목적

① 안정된 원격 호출의 제공

② 서버에서 애플랫으로의 콜백 제공

③ 분산모델을 자바환경으로의 통합

④ 분산 객체와 비분산 객체의 명확한 구분

⑤ 안정된 분산 애플리케이션을 간편하게 만들수 있는 환경제공

⑥ 자바 런타임환경에의해 제공되는 안전성 유지

 

3. RMI 의 Interface 와 Class

- java.rmi 안에 정의 되어 있는 인터페이스들과 클래스들은 RMI System 의 상태(behavior)들을 기술하고 있다. 밑의 그림은 인터페이스들과 클래스들간의 관계를 나타낸 것이다.

 

 

 

 

 

 

 

 

 

 

 

 

4. RMI 시스템의 구조

- RMI 시스템은 stub/skeleton 래이어, remote reference 래이어, transport 래이어로 구성되어 있으며 각 래이어의 경계는 특별한 인터페이스와 프로토콜에 의해 정의되진다. 또한 각 래이어는 서로 독립적이며 따라서 다른 래이어에 영향을 주지 않고 한 래이어의 성질을 바꿀수 있다. 예들들어 설명하자면, 현재 transport 구현은 TCP를 바탕으로 하고 있으나 다른 래이어에 영향을 주지 않고 UDP 를 바탕으로한 transport 로의 교체가 가능한 것이다.

 

- 또 다른 테크닉으로 Dynamic stub loading 이 있다. 이는 remote interface를 구현하는 client-side stub 를 지원하기 위해 쓰인다. 예들들면 정확한 형의 stub 가 클라이언트에서 유효하지 않을경우 클라이언트가 자바에 내장된 연산자를 캐스팅과 타입채킹에 사용하도록 허용한다.

① stub/skeleton

- 클라이언트가 remote server의 객체를 호출할때 실재로 호출되는 부분으로 stub는 원 격 인터페이스의 구현으로 호출 요청을 remote reference layer를 통해 서버 객체로 보내는 역할을 담당한다. skeleton 은 그 반대로 생각하면 된다.

② remote reference layer

- 호출 정보를 분석해서 server 가 단일 객체인지 아니면 여러 사이트에 흩어져 있는 복수개의 객체인지를 판별후 호출을 실행한다. 또한 그 서버가 항상 실행상태인지 아니면 호출시에만 실행이되는지에 따른 참조방법을 추상화 한다. 따라서 상위 래이어에서는 이러한 차이점이 보이지 않게 된다.

③ transport layer

- 연결 setup, 연결 관리, 그리고 연결 감시등을 책임지는 래이어다. 각 래이어의 경계는 특별한 인터페이스와 프로토콜에 의해 정의되지며 각 래이어는 서로 독립적이며 따라서 다른 래이어에 영향을 주지 않고 한 래이어의 성질을 바꿀수 있다. 예들들어 설명하자면, 현재 transport 구현은 TCP를 바탕으로 하고 있으나 다른 래이어에 영향을 주지 않고 UDP 를 바탕으로한 transport 로의 교체가 가능한 것이다.

 5. RMI의 원리


 














6. RMI 순서

① 로컬 객체는 원격 객체의 메서드를 호출

② 스텁은 해당 메서드를 호출할 때 넘겨받은 인자들을 전달하기 위해, 원격지의 스켈레톤과 네트워크에서 통신하여, 해당 메서드를 호출

③ 원격지의 스켈레톤은 로켈 객체의 스텁으로부터 네트워크를 통하여 메서드 이름과 인자를 넘겨받는다.

④ 원격 객체에서 메서드가 실행되고 결과는 스켈레톤으로 넘겨진다.

⑤ 넘겨받은 스켈레톤이 결과를 로컬 컴퓨터의 스텁으로 넘겨준다.

⑥ 스텁은 스켈레톤에게 넘겨받은 결과를 원래의 로컬 객체에게 반환값으로 넘겨주게 된다.

 

7. RMI 프로그래밍 방법

 

















8. RMI 프로그래밍의 흐림

① 원격 인터페이스를 정의

② 원격객체를 프로그래밍

③ 원격객체의 클래스를 얻는다.

④ rmic 컴파일러로 스텁과 스켈레톤을 생성

⑤ 로컬객체를 프로그램화

⑥ 로컬객체를 컴파일

⑦ 자바보안정책 파일을 수정

⑧ RMI 레지스트리 서버를 실행

⑨ 원격 객체 클래스를 실행

⑩ 로컬 객체를 실행하여 RMI 기능을 이용


RPC(Remote prodecure call)

 

1. RPC의 정의

- 다른 네트워크의 컴퓨터에 있는 함수를 호출할 수 있도록 해주는 저 수준의 프로세스간 통신 방법

- 네트워크 프로그래밍을 함수 호출 레벨 수준으로 작성할 수 있게 해주는 API

- OSF DEC(Open Software Foundation Distributed Computing Environment)

① Microsoft SQL과 같은 RPC 활용 클라이언트/서버 모델 지원

② 클라이언트의 LPC가 네트워크 메시지로 변환되는 RPC 모델 지원

③ Lotus Notes에서 사용되는 것과 같은 형태의 데이터 공유 모델 지원

 

2. RPC의 유용성

① 서버의 CPU까지 공유할 수 있는 기능 제공

② 네트워크에서 이용 가능한 가장 강력한 CPU를 사용할 수 있도록 해준다

③ 네트워크에 연결된 컴퓨터들이 CPU를 편중되지 않게 고루 사용할 수 있다

④ Remote의 함수를 Local 함수와 동일하게 호출할 수 있다

⑤ 네트워크 transport나 protocol의 종류에 영향을 받지 않고 사용할 수 있다

 

3. RPC 메터니즘

- 서브/클라이언트 모델을 따르기 때문에, 데이터 통신을 위한 메커니즘은 단순하다고 할 수 있다. 클라이언트에서 서비스를 요청하면 서버에서 프로시져를 호출해서 처리하고 리턴값을 되돌려주는 방식이다.

 

4. RPC 데이터 흐름

① Client 데이터는 XDR 필터를 통과해서 인코딩된 후 전달된다.

② XDR 인코딩된 데이터는 네트워크를 가로질러서 원격지 호스트로 전달된다.

③ 데이터를 받은 서버는 XDR 필터를 통해서 디코딩한다.

④ 디코딩된 데이터를 처리하고 결과 데이터를 XDR 필터를 통해서 인코딩한다.

⑤ 인코딩된 XDR 데이터가 네트워크를 가로질러서 클라이언트에 전달된다.

⑥ 클라이언트는 XDR 디코딩을하고 결과를 처리한다.

 

5.RPC의 처리 과정

1) 작동순서

① 클라이언트의 작업 요청

② 입력 인수를 NDR(표준 네트워크 포맷)로 전환

③ 작업 요청 내용을 서버로 전송

④ 클라이언트의 작업 요청 내용 수신

⑤ NDR을 서버 함수가 이용할 수 있도록 전환

⑥ 서버 함수 호출

⑦ 작업 결과 반환

⑧ 출력 인수를 NDR로 전환

⑨ 작업 결과를 클라이언트로 전송

⑩ 서버로부터 전송된 작업 결과 수신

⑪ NDR의 작업 결과를 전환

⑫ 작업 결과를 받고, 하던 작업을 계속 수행

2) stub의 역할

- 주소공간에서 필요한 인수를 가져옴

- 인수들을 네트워크에 전송할 수 있는 형태(network transmission format)로 전환

- 작업을 요청 또는 반환하기 위해 RPC 라이브러리 호출

RMI와 RPC의 비교

- 프로그래머를 네트워크로부터 어느 정도 격리시켜준다는 공통점을 가지고 잇다. 그래서 RMI와 RPC 프로그래밍 모두 프로그래머가 네트워크에 대해 많은 신경을 쓰지 않고도 프로그램을 개발할 수 있도록 해준다. RMI와 RPC의 차이점은 RMI는 메소드 호출이 가능한 방식을 말하며, RPC는 단순히 다른 컴퓨터에 존재하는 프로그램의 프로시져 수행을 위한 것이다. RPC에 객체 지향이라는 옷을 입힌 것이 RMI라 볼 수 있다.

Posted by 마블(이환문)
프로그래밍/OpenGL2009. 4. 29. 12:21

1. Why is the use of Texture Mapping

- 적은 수의 폴리곤으로 효과적인 렌더링을 할 수 있다.

 

1.2. Steps of Texture Mapping

① 텍스처 오브젝트를 만들고, 오브젝트에 해당하는 텍스처를 명시해야 한다.

② 텍스처가 어떻게 각각의 픽셀과 연관되어 있는지에 고나해 나타내어야 한다.

③ 텍스처 매핑을 활성화 한다.

④ 텍스처와 기하학적 좌표가 제공된 장면을 렌더링 한다.

 

2. Example Texture Mapping

AUX_RGBImageRec *LoadBMPFile(char *filename)
{
	FILE *hFile = NULL
	if(!filename) 
		return NULL
	
	if(hFile = fopen(filename, "r"))
	{
		fclose(hFile);
		return auxDIBImageLoad(filename);
	// BMP 정보를 리턴해 준다
	}

	return NULL
}

void CreateTexture()
{
	AUX_RGBImageRec *texRec[1];	// RGB정보를 가지는 구조체 정의							// 텍스쳐매핑초기화
	if(texRec[0] = LoadBMPFile("img.bmp")) 
	{
		glGenTextures(1, &g_Texture[0]); 
		// 객체의 이름을 만들어 준다
		// 첫 번째 인자: 몇 개의 텍스쳐 텍스쳐 맵핑 소스를 생성할 것인지 지정
		// 두 번째 인자: 첫 번째 텍스쳐 맵핑 소스의 식별자의 주소값
		glBindTexture(GL_TEXTURE_2D, g_Texture[0]); 
		//앞으로 설정할 텍스쳐의 식별자를 지정
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

		//GL_TEXTURE_MIN_FILTER: 맵핑 소스가 맵핑되어질 물체보다 클 경우
		//GL_TEXTURE_MAG_FILTER: 맵핑 소스가 맵핑되어질 물체보다 작을 경우
		//GL_NEAREST_MIPMAP_LINEAR   OpenGL   고화질
		//GL_NEAREST_MIPMAP_NEAREST  OpenGL   저화질. 권장.
		//GL_NEAREST_MIPMAP_LINEAR   OpenGL   고화질
		//GL_LINEAR_MIPMAP_NEAREST   Direct3D 저화질. 권장.
		//GL_LINEAR_MIPMAP_LINEAR    주로 배경(벽이나 지면의 재질감)의 설정
		//GL_LINEAR : 보간법
		//GL_NEAREST : 최근점법
		glTexImage2D(GL_TEXTURE_2D, 0, 3, texRec[0]->sizeX, texRec[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, texRec[0]->data);
		// 두 번째 인자 : 항상 0, 세 번째 인자: 색상 구성 요소의 개수 RGB 3		   개,여섯번째: 이미지 경계에 대한 픽셀 두께, 여덟 번째: 색상 구성 요		   소들이 갖는 값의 타입
	}

	if(texRec[0])		// 할당된 메모리를 해제
	{
		if(texRec[0]->data)
		{
			free(texRec[0]->data);
			free(texRec[0]);
		}

	}	

	glEnable(GL_TEXTURE_2D);		//텍스쳐 맵핑 사용함을 알림
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

void RenderScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	gluLookAt(0, 0, 6,	0,0,0,	0,1,0);

	glBindTexture(GL_TEXTURE_2D, g_Texture[0]);
	glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 1.0f);
		glVertex3f(-1, 1, 0);

		glTexCoord2f(0.0f, 0.0f);
		glVertex3f(-1, -1, 0);

		glTexCoord2f(1.0f, 0.0f);
		glVertex3f(1, -1, 0);

		glTexCoord2f(1.0f, 1.0f);
		glVertex3f(1, 1, 0);
	glEnd();

	SwapBuffers(g_hDC);
	
}

※ texel - 텍스처 데이터(컬러, 명도, 컬러와 알파 데이터)들의 사각형 배열 각각의 값
※ tessellation - 폴리곤의 각 꼬지점만을 명시해주면 자동으로 폴리곤을 오목한 부분이 없도록 쪼개서 연출해주는 기능

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

옥트리(octree)  (0) 2009.04.25
OpenGL Viewing  (0) 2009.04.24
OpenGL 기초  (1) 2009.04.23
Picking 사용하기  (0) 2009.04.22
void glutIdleFunc(void (*func)(void))  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/Multimedia2009. 4. 28. 18:07

1. DCT(이산여현변환)란?

- DCT를 이용하면 영상을 공간영역(Spatial Domain)으로부터 주파수 영역(Frequency Domain)으로 변환할 수 있으며 영상 데이터는 변화가 적으므로 낮은 주파수, 특히 0 주파수(DC) 성분이 큰 값을 갖게 되고 높은 주파수 성분은 상대적으로 매우 작은 값을 갖게 된다. 즉, 대부분의 정보가 낮은 주파수 쪽으로 몰리게 되므로 다음에 설명할 양자화 과정을 적절히 거치면 높은 압축률로 우수한 화질을 얻을 수 있다.

- 정보량이 많아 값이 낮은 주파수 성분만을 충실히 취하고 높은 주파수 성분은 값이 아주 작으므로 거의 무시해 버림으로써 전체 데이터양을 줄이는 원리인데, 높은 주파수 성분은 영상의 세밀한 해상도에 기여하기 때문에 이런 방법을 사용해 심하게 압축하면 해상도 저하라는 불이익을 감수해야 한다. DCT는 많은 곱하기와 더하기가 사용되므로 계산량이 상당히 많으나, 고속 알고리즘으로 인해 영상을 8*8 블럭으로 나눠 계산함으로써 계산 효율을 높일 수 있다.이 DCT를 용한 영상압축 방법은 성능과 실제 구현성이 좋아 정지 영상 뿐 아니라 동영상 압축에서도 폭 넓게 사용되고 있다.

 

1.1 DCT의 정의

- 두 개의 정수 값 i, j에 의해 표현된 함수(한 정지 영상)가 주어졌을 때 2D DCT 변환은 이 함수를 i와 j와 동일한 범위를 가지는 정수 u, v로 표현되는 새로운 함수로 변환된다.

i, u = 0,1,...,M-1 j, v = 0,1,...,N-1

- JPEG 영상 압축 표준에서 영상 블록은 차원 M = N = 8을 갖는 것으로 정의된다.

 

2. 2D DCT(2차원 이산 여현 변환)

i, j, u, v = 0,1,...,7

2.1 2D IDCT(2차원 역이산 여현 변환)

i, j, u, v = 0,1,...,7

3. 1D DCT

- f(i)가 시간 i에 따라 변화하는 신호를 표현.

- 시공간의 신호 f(i)를 주파수 공간의 신호 F(u)로 변환.

3.1 1D DCT의 예

 

예) f(i)가 (a)와 같이 주어졌을 경우에 F(u)를 구하시오

예) (d)는 임의의 입력 신호 f(i)에 대한 그 DCT 출력 F(u)를 나타내고 있다.

f(i)(i=0...7) : 85 -65 15 30 -56 35 90 60

F(u)(u=0...7) : 69 -49 74 11 16 117 44 -5

임을 보이시오.

 

3.2 1D IDCT

예) F(u)(u=0...7) : 69 -49 74 11 16 117 44 -5 값이 다음과 같을 때 f(i)를 구하시오

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

디지털 오디오의 기초  (0) 2009.04.27
Posted by 마블(이환문)
프로그래밍/Multimedia2009. 4. 27. 13:55

1. 사운드의 디지털화

1.1 사운드란 무엇인가?

- 불빛과 같은 파동 현상을 말하기도 하고, 어떤 물리적인 장치의 동작에 의해 공기가 압축되고 팽창하는 분자 운동과 관련된다.

 

1.2 디지털화

- 아날로그 신호를 디지털화하기 위해서 sampling을 수행하여야 함.

- sampling : 균일한 시간 간격으로 값을 측정하는 방법.

- sampling frequency : sampling이 취해지는 비율.

- quantization : 진폭이나 전압의 sampling을 의미.

그림 1. 아날로그 신호의 예

1.3 Nyquist 이론

- 정확한 sampling을 위해서는 적어도 신호의 최대 주파수 성분의 두 배로 sampling이 이루어져야 한다.

- Nyquist 율과 동일한 주파수를 의미.

그림 4. 구형파 신호의 중첩

1.4 Aliasing

- 분석하고자 하는 신호를 sampling 주파수로 sampling하여 주파수 분석을 하였을 때 sampling 주파수의 절반 이상에 해당하는 주파수성분이 Nyquist 주파수 이하의 성분으로 겹쳐 나타나게 되는 현상

 

그림 7. 주파수의 1.5배 sampling

 

1.5 신호 대 잡음 비(SNR)

- 신호 대 잡음 비(SNR) : 정확한 신호와 잡음의 비율을 의미.

- SNR값은 dB을 단위로 하고 전압의 제곱에 대한 상용로그 값으로 정의됨.

 

P is average power, A is RMS amplitude

 

예제) 신호의 전압 이 잡음의 10배라면, SNR은 이다.

 

 

 

 

1.6 신호 대 잡음 비(SQNR)

- 양자화 잡음 : 원신호에 비교하여 양자화된 아날로그 신호의 오차분

- 샘플당 N 비트에 대한 디지털 신호의 범위는 에서

- 실제 아날로그 신호가 에서 범위에서 존재한다면, 각 양자화 단계는 이걸로 표시한다.

- SQNR은 단지 신호의 최대값으로 표현되고, 이것은 약 의 단계 로 대응.

- SQNR은 분모로 최대값 = 를 가진다

6.02N(dB)는 피크 신호 대 잡음비(PSQNR)을 나타내고 있다.

최악의 경우로 입력파가 구형파라면 아래와 같은 수식을 가진다

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

DCT(Discrete Cosine Transform)  (0) 2009.04.28
Posted by 마블(이환문)
프로그래밍/C++2009. 4. 25. 20:03

회사에서나 개인적으로 S/W를 전문적으로 개발해 본 경험이 있는 분이라면 로깅 라이브러리는 거의 모든 프로젝트에 공통적으로 필요한 모듈이라는 걸 인정하실 것입니다.

로깅 라이브러리는 개발하는 중간에는 S/W 가 잘못 수행되는 부분은 없는지 디버깅하는 데 유용할 뿐만 아니라 개발이 완료되고 나서 시스템 운영자나 관리자가 해당 S/W를 운영하는 데 필수적인 정보를 제공하는 데 유용합니다. 여러분이 웬만한 규모 이상의 S/W를 개발한다면 항상 맞닥뜨려야 하는 문제라는 것이죠. 이렇게 거의 모든 S/W에서 필요한 라이브러리임에도 불구하고, 잘 설계된 로깅 라이브러리를 만든다는 건 쉽지 않은 일입니다.

이런 로깅 라이브러리를 설계/구현하는 긴 여정을 이번 글을 시작으로 해서 떠나 보도록 하겠습니다.

아~! 근데 본격적으로 여정을 떠나기 전에 한 가지 준비를 하고 떠나야 되겠습니다. 로깅을 위해 C/C++ 언어에서 제공하는 기본 메커니즘들을 확인하고 가는 것이죠. 이걸 확인하는 방법은 다양하겠지만, 저는 assert()를 통해 설명드리도록 하겠습니다.

assert() 란 함수의 본래 로직을 수행하기 전/후에 해당 로직이 가정하고 있는 특정 조건이 만족하는지를 체크해 보고 만족하지 않으면 실행을 중단해 버리는 특수한 함수로 개발 도중 디버깅 용도로 많이 쓰이는 방법입니다.

예를 들어, 여러분이 C 라이브러리 구현자라고 상상해 보시겠습니다. 그리고, memcpy() 와 같은 함수를 구현하는 책임이 있다고 상상해 보겠습니다. memcpy()는 다음과 같이 구현할 수 있을 것입니다.

/*
 * memcpy()는 pvSource 로부터 pvSink 로 sz 만큼을 복사함.
 * memcpy()는 pvSource 와 pvSink 가 NULL 이 아니라는 걸 가정하고 있음
 */
void*
memcpy(void* pvSink, void* pvSource, size_t sz)
{
  char* pSrc = (char*)pvSource;
  char* pSnk = (char*)pvSink;

  while (sz-- > 0)
    *pSnk++ = *pSrc++;

  return pvSink;
}

기본적인 아이디어는 위와 같이 구현할 수 있지만, memcpy 가 가정하고 있는 pvSource와 pvSink 가 NULL 이 아니라는 건 체크하지 않고 있기 때문에 어떤 개발자가 실수로 pvSink 나 pvSource에 NULL을 주게 되면 실행시에 심각한 문제가 발생할 수도 있습니다. 이런 문제를 피하기 위해 당연히 본래의 로직을 실행하기 전에 pvSource 와 pvSink의 NULL 여부를 체크하는 로직을 넣어야 할 것입니다.

/*
 * pvSource, pvSink가 NULL인지를 체크함
 */
void*
memcpy(void* pvSink, void* pvSource, size_t sz)
{
  char* pSrc = (char*)pvSource;
  char* pSnk = (char*)pvSink;

  /* pvSource 나 pvSink가 NULL 프로그램 수행을 중단 */
  if (pvSource == NULL || pvSink == NULL) {
    fprintf(stderr, "memcpy(): Bad args: pvSource or pvSink is NULL\n");
    abort();
  }

  while (sz-- > 0)
   *pSnk++ = *pSrc++;

  return pvSink;
}

어떤 분은 NULL 인지 체크한 후 에러 메시지만 출력하고 바로 리턴하는 게 아니라 abort()를 호출하는 걸 이상하게 생각하실 분도 있을 것 같습니다. 그건 pvSource 나 pvSink의 값으로 NULL이 넘어 왔다면 프로그램에 뭔가 심각한 버그가 있다는 걸 뜻하기 때문입니다. 뭔가 심각한 버그가 있는 채로 실행되는 것 보다는 그 상황이 알려지면 빨리 종료해 버리는 것이 문제를 해결하는 데 훨씬 유리하기 때문입니다. 그렇다 하더라도 이미 S/W가 출시된 상황에서 그런 일이 벌어진다면 고객으로부터 Claim이 올 것은 자명한 일입니다. 따라서 pvSource 나 pvSink 가 NULL 인지 체크하는 코드는 Production 시스템에 사용되는 것이 아니라 개발시에 사용되어야 그 의미가 살아날 수 있습니다. 즉, 다음과 같이 작성해야 할 것입니다.

/*
  * pvSource, pvSink가 NULL인지를 체크함
  */
void*
memcpy(void* pvSink, void* pvSource, size_t sz)
{
  char* pSrc = (char*)pvSource;
  char* pSnk = (char*)pvSink;

#ifdef DEBUG
  /* pvSource 나 pvSink가 NULL 프로그램 수행을 중단 */

  if (pvSource == NULL || pvSink == NULL) {
    fprintf(stderr, "memcpy(): Bad args: pvSource or pvSink is NULL\n");
    abort();
  }
#endif

  while (sz-- > 0)
   *pSnk++ = *pSrc++;

  return pvSink;
}

위와 같이 작성하고 개발시에는 DEBUG 를 정의해서 컴파일하고, 정식 릴리즈시에는 DEBUG를 정의하지 않고 컴파일하면 개발시에만 abort()가 적용되어 디버깅하는 데 도움을 얻을 수 있게 됩니다.

이렇게 뭔가 만족되어야 하는 조건을 검사하고, 그 조건이 만족하지 않을 경우 실행을 중단해버리는 함수가 바로 assert() 입니다. 다음은 assert() 를 적용해서 위 코드를 수정해 본 것입니다.

/*
  * pvSource, pvSink가 NULL인지를 체크함
  */
void*
my_memcpy(void* pvSink, void* pvSource, size_t sz)
{
  char* pSrc = (char*)pvSource;
  char* pSnk = (char*)pvSink;

  assert(pvSource != NULL && pvSink != NULL);

  while (sz-- > 0)
   *pSnk++ = *pSrc++;

  return pvSink;
}

char* pSource = "source memory content";
char pSink[32];

/* main() 함수 */
int
main(void)
{
  printf("Calling my_memcpy(%08x,%08x, %d)\n",
    pSink, pSource, strlen(pSource));
  my_memcpy(pSink, pSource, strlen(pSource));
  /* assert()가 실패하는 케이스 */
  printf("Calling my_memcpy(NULL,%08x, %d)\n", pSource,
    strlen(pSource));

  my_memcpy(NULL, pSource, strlen(pSource));
}

다음은 GCC(3.4.4)로 컴파일하고 실행해 본 결과입니다.

$ ./debug
Calling my_memcpy(00404020,0040302b, 21)
Calling my_memcpy(NULL,0040302b, 21)
debug: debug.c:15: main: Assertion `pvSource != NULL && pvSink != NULL' failed.
Aborted


위 결과에서 보시다시피 두 번째 my_memcpy() 실행 중에 assert()가 실패해서 실행이 취소되는 걸 확인할 수 있습니다.

assert()가 실패했을 때, 출력해주는 메시지를 살펴 보시기 바랍니다. 프로그래머가 특별히 입력인자로 주지도 않았는데 다음과 같은 부가적인 정보를 출력해서 디버깅이 용이하도록 해줍니다.

1. debug.c 라는 파일명을 출력해 주네요
2. 정확한 라인번호도 출력해 줍니다.
3. pvSource != NULL && pvSink != NULL 이라는 수식을 문자열로 출력해 줍니다.

너무 신기하지 않나요 ? 도대체 assert() 란 녀석이 어떤 마술을 부리길래 프로그래머가 주지도 않은 정보를 알아내서 저런 정보를 출력해 줄 수 있을까요 ? 전 처음에 이 assert() 란 녀석을 써보고서는 어찌나 신기했던지 어떻게 구현된지를 알아낼려고 한참을 헤멨습니다. 여러분도 궁금하시죠 ? 그럼 지금부터 하나 하나 그 비밀을 파헤쳐 보도록 하겠습니다.

assert() 란 녀석이 어떻게 구현되어 있는지를 알아내면 그 비밀을 알아낼 수 있을 것 같습니다. 우선 assert() 가 선언되어 있는 헤더 파일부터 뒤져보기로 하죠.

제가 지금 이 글을 쓰면서 쓰고 있는 컴파일러는 GCC 인데요, 그곳에서는 다음과 같이 assert()가 정의되어 있습니다.

/* 이해를 돕기 위해 간략하게 표현했습니다 */
extern void __assert_fail (__const char *__assertion,
                           __const char *__file,
                          
unsigned int __line,
                           __const char *__function)


#define assert(expr) \
 
((void) ((expr) ? 0 : (__assert_fail (#expr, __FILE__,  \
                                       __LINE__, __func__), 0)))


일단 이걸 보면 assert() 가 그냥 함수가 아니라 매크로 함수인 것을 알 수 있습니다. 그리고 내부적으로는 __assert_fail() 이라는 또 다른 함수를 #expr, __FILE__, __LINE__, __func__ 라는 인자를 가지고 호출하는 걸 확인할 수 있습니다. 도대체 이런 듣도 보도 못한 애들은 어느 별에서 온 걸까요 ? 참 신기하긴 합니다만 신기해 하는 것으로 그쳐서는 안되고 진정한 골수 엔지니어라면 얘네들이 어떤 별에서 왔는지 무슨 일이 있어도 알아내야 되겠지요.

우선 #expr 부터 살펴 보겠습니다. 다음 코드를 한 번 컴파일하고 실행시켜 보시겠어요 ?

#include <stdio.h>

#define STRINGIFY(x) #x

int
return10(void)
{
  return 10;
}

int
main(void)
{
  printf("%s = %d\n", STRINGIFY(10 + 100), 10 + 100);
  printf("%s = %d\n", STRINGIFY(strlen("printf")), strlen("printf"));
  printf("%s returned %d\n", STRINGIFY(return10()), return10());
}

컴파일한 후 실행시켜 보았더니 이런 결과가 나오네요.

$ gcc -o stringify stringify.c
$ ./stringify
10 + 100 = 110
strlen("printf") = 6
return10() returned 10

어~! STRINGIFY()를 호출하면 STRINGIFY로 넘겨준 인자가 그냥 그대로 문자열 리터럴이 되어 버리네요. 아~! 그렇구나. 매크로 함수로 넘겨준 인자에 '#'를 붙이면 전처리기가 그걸 문자열 리터럴로 바꿔주나 봅니다. 그것 참 쓸모 있을 것 같습니다.

다음은 __FILE__, __LINE__, __func__ 를 알아보도록 하겠습니다.

__FILE__ 매크로는 컴파일시에 파일명을 나타내는 문자열 리터럴로 치환되고 __LINE__ 은 소스 코드 라인번호로 치환됩니다. 그리고, __func_ 는 매크로가 아니라 컴파일러에 의해 자동으로 선언되는 변수로서 함수명을 나타내는 문자열을 가리키게 됩니다. __FILE__ 은 문자열 리터럴이고, __LINE__ 은 정수 리터럴이고, __func__ 는 자동으로 정의되는 변수라는 점에 주의해야 합니다. __func__ 와 비슷한 역할을 하는 녀석이 __FUNCTION__ 이라는 녀석입니다. 다음 코드를 컴파일해서 실행해 볼까요 ?

/* debug2.c */
#include <stdio.h>
#include <assert.h>

int
main(void)
{
  printf("%s() at %s::%05d\n", __FUNCTION__, __FILE__, __LINE__);
  printf("%s() at %s::%05d\n", __func__, __FILE__, __LINE__);
  assert(1 == 0);

  return 0;
}

Windows XP 에서 cygwin GCC(3.4.4)로 컴파일해서 실행해 봤더니 다음과 같은 결과가 나오는 군요.

$ ./debug2
main() at debug2.c::00009
main() at debug2.c::00010
assertion "1 == 0" failed: file "debug2.c", line 11
     38 [sig] debug2 5940 C:\cygwin\home\김윤수\work\debug2.exe: *** fatal error - called with threadlist_ix -1
Hangup

주의할 점은 __FUNCTION__ 은 비표준으로 컴파일러 벤더들이 자체적으로 제공하는 확장 매크로로 preprocessing 중에 문자열 리터럴로 치환되는 반면 __func__ 는 C99 표준에 포함된 것으로 컴파일러에 의해 자동적으로 정의되는 const char * 형의 변수라는 점입니다.

일단 GCC는 __FUNCTION__ 과 __func__ 둘 다를 지원하는 반면 Microsoft Visual C++ 2005의 cl은 __FUNCTION__ 만 지원하는 군요. 또 신기한 점은 GCC, G++은 __FUNCTION__ 을 __func__ 와 동일하게 처리한다는 점입니다. 위에서 컴파일해 본 debug2.c 에 대해 preprocessing 한 결과를 살펴 보면 매크로가 아니라 변수로 취급하는 걸 알 수 있습니다.

$ gcc -E debug2.c > debug2.pre.c
$ cat debug2.pre.c
......

int
main(void)
{
  printf("%s() at %s::%05d\n", __FUNCTION__, "debug2.c", 10);
  printf("%s() at %s::%05d\n", __func__, "debug2.c", 11);
  ((1 == 0) ? (void)0 : __assert("debug2.c", 12, "1 == 0"));

  return 0;
}

위에서 첫번째 명령에서 gcc 의 -E option은 preprocessing 한 결과를 표준출력으로 내보내라라는 옵션입니다. 위 명령들은 그걸 리다이렉션해서 debug2.pre.c 라는 파일로 저장해서 그 내용을 살펴본 것입니다. (각 컴파일러에서 preprocessing 만 하게 하는 옵션을 알아두면, 매크로 치환하면서 문제가 발생하는 경우 쉽게 디버깅할 수 있습니다. 꼭 알아 두시기를 권유합니다.)

__FUNCTION__, __func__ 이 매크로였다면 preprocessing 후에는 둘 다 __FILE__ 처럼 문자열 리터럴로 치환되어야 함에도 원래 이름대로 그대로 남아 있는 것은 전처리기가 처리하지 않는다는 뜻이 될 것입니다.

위에서 assert() 가 어떻게 정의되어 있는지 보여드렸을 때는 간략하게 하기 위해 복잡한 조건 컴파일을 보여드리지 않았는데요, 실제로는 다음과 같이 NDEBUG(No debugging 의 약자라고 보시면 됩니다)가 정의되어 있느냐에 따라 조건 컴파일이 되도록 정의되어 있습니다.

#ifdef NDEBUG /* required by ANSI standard */

#define assert(expr)      ((void)0)

#else

/* 아까 나온 부분. 이해를 돕기 위해 간략하게 표현했습니다 */

......
#define assert(expr) \
 
((void) ((expr) ? 0 : (__assert_fail (#expr, __FILE__,  \
                                       __LINE__, __func__), 0)))

#endif

즉, NDEBUG 가 정의되어 있으면(다르게 표현하면 디버깅 버전이 아니라는 뜻이겠지요), assert() 가 아무런 영향을 미치지 않고, 아무것도 정의되어 있지 않으면 assert()가 영향을 미치는 것이겠지요. NDEBUG는 ANSI 표준에 의해 정의된 매크로이기 때문에 대부분의 컴파일러가 지원할 것으로 보입니다. 앞에서 작성했던 debug2.c 를 다음과 같이 NDEBUG를 정의한 채로 컴파일한 후 실행하면 assert()가 아무런 영향을 미치지 않는 것을 확인할 수 있습니다.

$ gcc -o debug2 -DNDEBUG debug2.c
$ ./debug2
main() at debug2.c::00010
main() at debug2.c::00011


이렇게 디버깅 버전과 Production 버전에 따라 여러 가지 코드가 포함되고 포함되지 않게 하는 것은 실제 프로젝트에서 일상적으로 활용되는 방법입니다. 개발하는 동안에는 디버깅 버전으로 원래의 실행 로직외에 다양한 코드 또는 정보들이 실행 파일에 포함되게 함으로써 문제가 발생했을 때 신속하게 찾을 수 있게 도와주고, Production 버전에서는 성능을 개선하고 실행파일의 크기를 줄이기 위해 그런 부가적인 정보 또는 코드를 없애버리는 것이지요

마지막으로 한 가지 더 주의할 점을 알려 드리겠습니다. __FUNCTION__ 은 컴파일러 벤더들이 나름대로 정의한 확장 매크로이기 때문에 portability 가 떨어지고, __func__ 는 최신 C99 규격에 포함된 것이라 portability 가 약간 떨어진다는 것입니다. 물론 장기적으로는 __func__ 를 쓰는 것이 유리하겠지만(표준이니까 __func__ 를 지원하는 컴파일러가 많이 생길 것으로 기대할 수 있으므로), Microsoft Visual C++ 2005 버전도 지원하지 않는 걸 보니 __func__도 본격적으로 쓰일 수 있으려면 아직 갈 길이 먼 것 같습니다. 정리하자면 portability 가 중요하다면 __FUNCTION__ 이나 __func__ 를 전혀 쓰지 않거나 아니면 쓰더라도 컴파일러에서 __FUNCTION__ 이나 __func__이 지원되는 여부에 따라서 다르게 처리되도록 해야할 것입니다. 더 상세한 정보를 얻기 원하시면 다음 링크를 참고하시기 바랍니다.

__LINE__, __FUNCTION__, and __FILE__ macros
C, C++ 언어에서 디버깅시에 유용하게 쓸 수 있는 predefined macro 에 대한 설명
Pre-defined C/C++ Compiler Macros
다양한 C/C++ 컴파일러에서 정의된 표준 및 비표준 predefined macro 목록을 잘 정의해 둔 곳

이번 글에서는 다음과 같은 사항을 기억하시면 될 것입니다.

  1. 매크로 함수 정의에서 인자 x 에 대해 #x 라고 쓰면 x 가 문자열 리터럴로 바뀝니다.
  2. __FUNCTION__ 또는 __func__ 는 현재 함수명을 출력하는 데 사용할 수 있습니다. 단, __FUNCTION__, __func__ 모두 portability 측면에서 약간 문제가 있으므로 주의해서 사용해야 합니다.
  3. __FILE__ 은 현재 파일명을 출력하는 데 사용할 수 있습니다.
  4. __LINE__ 은 현재 라인번호를 출력하는 데 사용할 수 있습니다.
  5. 조건부 컴파일을 이용해서 디비깅용 빌드와 Production 빌드를 구분하는 방법을 사용할 수 있어야 합니다.
  6. 컴파일러 옵션 중 preprocessing 만 하는 옵션을 알아두면 매크로 함수를 디비겅하는데 유용하게 사용될 수 있습니다.

자~ 그럼 이상으로 해서 로깅 라이브러리를 설계/구현하기 위한 여정에 필수적인 도구들을 다 챙긴 것 같습니다

출처: 김윤수의 이상계를 꿈꾸며
url : http://yesarang.tistory.com/74

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

C++ 디버깅시 TRACE 사용하기  (0) 2009.12.10
STL - List 사용하기  (0) 2009.07.16
파일 입출력 및 링크드 리스트  (1) 2009.05.01
Abstract Factory Pattern  (0) 2009.04.22
PVOID  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/OpenGL2009. 4. 25. 20:03

Octree

정의

옥트리는 삼차원 공간에서 오브젝트를 표현하기 위한 자료구조이다. 이는 자동적으로 그것들을 계층화하여 그룹화하고, 공간 내의 빈 부분에 대한 표현을 피한다.

첫 번째 옥트리 노드는 루트 셀인데, 아래의 그림에서 보이듯이 이는 여덟 개의 연속되는 요소들의 배열이다. 이들 각 요소들은 여덟 개의 연속된 요소들 중 다른 하나를 가리킬 수 있다. 여기에서 각각은 여덟 개의 연속되는 요소들 중 다른 하나를 가리키는 식인데, 이는 특정한 최대 숫자의 레벨에 도달할 때까지 계속된다. 마지막 레벨은 단말 레벨이며, 여기에서 단말 요소나 복셀(voxel)이 저장된다.

 
복셀 검색 원리

위의 그림은 자료 구조가 메모리(왼쪽)와 그것의 삼차원 공간 표현(오른쪽) 사이에서 어떻게 대응되는지를 보여준다. 트리는 여덟 개의 복셀과 다섯 개의 레벨을 가지고 있다. 각 레벨은 서로 다른 색으로 표현된다. 옥트리의 마지막 레벨에 저장되는 여덟 개의 레벨은 분홍색으로 표현된다. 위의 그림의 표는 마지막 셀에 있는 다섯번 째 복셀(index 4)의 좌표를 보여준다. 그것의 좌표는 : 31, 30, 30이며, 후에 오른쪽에서 이진 형식으로 보여진다. 마지막으로 각 좌표 비트와 연관된 각 열도 옥트리의 레벨과 관련이 있다. 각 열의 비트를 합치면, 관련 레벨을 위한 셀의 각 요소에 대한 (테이블의 마지막 열에 나온) 인덱스를 획득하게 된다. 그리고 이것은 트리를 순회하고 검색된 복셀을 찾는 데 사용된다. 이것이 옥트리에서 주어진 복셀을 검색하는 원리이다.

(역주 :

각 열을 합칠 때, x를 0비트, y를 1비트, z를 2비트로 취급하면, 1 + 2 + 4 = 7, 0 + 0 + 4 = 4가 된다. 위의 표에서 열이 나타내는 숫자 0, 1, 2, 3, 4는 복셀의 인덱스를 의미하고 있다.

그런데 표가 이상하다. 비트의 합이 0 ~ 7까지 순서대로 나오는게 맞을텐데 다 7이고 4번째 열만 4다

)

이러한 구조의 주요 이점은 복셀 좌표가 저장되지 않고, 순회되는 요소들의 위치에 의해 암묵적으로 내포된다는 것이다. 이것은 좌표가 저장하기에는 크고 성가신 고해상도 복셀 공간에서 매우 편리하다. 3D 그리드도 복셀 위치를 내포하는 좌표를 가지고 있지만, 대부분의 볼륨이 비어있는 경우에도 볼륨 내의 모든 복셀을 표현해야 한다는 심각한 단점을 가지고 있다.

메모리 점유

위의 단말 레벨에서 모든 셀의 요소들이 셀을 가리키고 있다면, 그 옥트리를 "꽉찬 옥트리(full octree)"라고 부른다. n개의 레벨을 가지고 있는 꽉찬 옥트리는 해상도 2n x 2n x 2n의 정규 3D 그리드와 같은 복셀 개수를 포함한다(예를 들어 꽉찬 옥트리가 5레벨이라면 32 x 32 x 32 해상도를 가진 3D 그리드와 같다). 이런 극단적인 경우에 옥트리는 관련 3D 그리드보다 14% 많은 방(역주 : 메모리공간인듯)을 차지한다. 그러나 복셀이 볼륨 대신에 서피스를 표현한다면, 대부분의 공간은 비어있으며 그것은 옥트리 내에 표현되지 않는다. 이런 경우에 옥트리는 매우 경제적이며, 적은 메모리 소비만으로 고해상도 복셀 공간을 만들 수 있게 해 준다

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

Texture Mapping  (0) 2009.04.29
OpenGL Viewing  (0) 2009.04.24
OpenGL 기초  (1) 2009.04.23
Picking 사용하기  (0) 2009.04.22
void glutIdleFunc(void (*func)(void))  (0) 2009.04.22
Posted by 마블(이환문)
프로그래밍/OpenGL2009. 4. 24. 14:03

뷰잉

I. 개요

A. 뷰잉 변환: 카메라가 Scene을 향하도록 설정한다

B. 모델링 변환: 원하는 장면을 화면에 담도록 모델을 구성한다

C. 투영 변환: 사용할 카메라 렌즈를 선택하거나 줌을 조절한다

D. 뷰포트 변환: 사진의 크기를 결정한다

E. 연산들의 순서: 뷰잉 변환이 모델링 변환보다 먼저 수행되도록 작성해야 하며, 투영 변환이나 뷰포트 변환은 드로잉 작업이 수행되기 전에 어느 곳에 나와도 상관없다.

1. 오브젝트 좌표 -> 모델뷰행렬 (눈 좌표) -> 투영행렬 (클립 좌표) -> 투시분할 -> 정규화 장치 좌표 -> 뷰포트 전환(윈도우 좌표)

II. 변환 행렬

A. 컴퓨터 그래픽을 이해하기 위해서는 가장 핵심적이고 기본이 되는 4x4 행렬 형식의 변환을 설명하는 것으로부터 시작된다.

B. 기본변환

1. 이 절에서는 평행 이동, 회전, 크기 같은 기본변환들을 알아본다

C. 평행이동(Translation)

1. 한 위치를 다른 위치로 변경하는 것은 평행이동 행렬 T로 표현된다. 이 행렬은 하나의 벡터 t=(tx,ty,tz)를 이용하여 개체를 이동시킨다.

2. T(t) = T(tx,ty,tz) = ( 1 0 0 tx )

( 0 1 0 ty)

( 0 0 1 tz)

( 0 0 0 1)

  



D. 회전(rotation)

1. 회전 행렬은 Rx(ɸ), Ry(ɸ), Rz(ɸ)에 의해 표시된다.

2. 이 행렬들은 하나의 개체를 x, y, z축으로 ɸ라디안 만큼 회전시킴을 의미한다.

Rx(a)= 

  Ry(a) =
 
Rz(a) =

E. 크기 조정(scaling)

1. 크기 조정 행렬 S(s) = S(x,y,z)는 한 개체를 x,y,z방향으로 각각 x배, y배, z배만큼 확대 축소한다.

2. 조정행렬

  


3. 만약 x = y = z 이면 크기 조정 연산은 균등(uniform)하다고 하고 그렇지 않은 경우는 비균등(nonuniform)하다고 한다.

F. 예제

1. 지금까지 각각의 변환 행렬들에 대해서 살펴보았다. 그렇다면 어떤 한 점을 어느 지점으로 이동 시킨 후에 다시 어떤 축을 중심으로 회전하고 크기를 변경시키고자 할 때 행렬이 어떤식으로 계산되어 지는지를 알아보도록 한다

2. p = (3,2,1)의 점을 X축으로 -1만큼 이동 후 z축으로 90 회전시킬때의 행렬

   =>  
 

3. 위 행렬은 아래와 같이 계산되어 질 수도 있음


G. 본 절에서 설명된 변환 행렬 이외에도 쉬어변환 강체 변환, 법선 벡터 변환등 다양한 행렬 표현이 존재한다

III. 뷰잉 및 모델링 변환

A. 고정 좌표계

1. 모델의 위치, 방향, 크기 등에 영향을 미치는 행렬 곱셈을 고정 좌표계 관점에서 생각하는 경우에는 코드에 나온 순서와 반대로 곱셈이 수행된다는 것을 명심해야 한다.

B. 로컬 좌표계 이동하기

1. 행렬 곱셈을 바라볼때 변환할 오브젝트가 고정 좌표계에 있지않고, 오브젝트에 로컬 좌표계가 달려 있는 것처럼 생각할 수 있다. 모든 연산들은 이러한 좌표계에 상대적으로 수행된다.

2. 모델링 변환

a) glTranslate{fd}(TYPE x, TYPE y, TYPE z);

(1) 인자로 지정된 x,y,z 값 만큼 오브젝트(또는 로컬 좌표계)를 평행 이동 시키도록 지정된 행렬과 현재 행렬을 곱한다.

b) glRotate{fd}(TYPE angle, TYPE x, TYPE y, TYPE z);

(1) 원점에서 x,y,z에 이르는 선을 기준으로 오브젝트(또는 로컬 좌표계)를 반시계 방향으로 회전시키는 행렬과 현재 행렬을 곱한다. angle 매개변수에는 회전할 각도를 지정한다

c) glScale{fd}(TYPE x, TYPE y, TYPE z);

(1) 오브젝트를 각 축을 따라 늘이거나 , 대칭이동 행렬과 현재 행렬을 곱한다. 오브젝트를 구성하는 점의 x,y,z 좌표는 인자로 주어진 x,y,z 값과 곱해진다.

d) glMultiMatrix

C. 뷰잉 변환

1. 뷰잉 변환을 사용하면 시점의 위치와 방향을 변경할 수 있다.

2. glTranslate와 glRotate 사용하기

a) 카메라는 초기에 z축의 음의 방향을 향하고 있다는 점에 주의

b) 오브젝트를 볼 수 있게 하기 위해서는 몇가지 변환을 수행해야 한다.

c) 시점을 오브젝트 뒤로 이동시킬 수 있다.

3. gluLookAt 유틸 사용하기

a) void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery,GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz);

b) 이 행렬은 뷰잉 행렬을 정의한 뒤 이를 현재 행렬의 오른쪽에 곱한다. 원하는 시점은 eyex, eyey, eyez로 지정한다. centerx, centery, centerz인자로 원하는 시선에 존재하는 임의의점을 지정할 수 있지만, 일반적으로 바라보는 장면의 가운데 지점을 지정할 때 사용한다. upx, upy, upz 인자는 위쪽에 해당하는 방향을 가리킨다

IV. 투영 변환

A. 기본형식

1. glMatrixMode(GL_PROJECTION)

2. glLoadIdenty();

B. 원근투영

1. 원근 투영의 가장 큰 특징은 바로 포쇼트닝(foreshortening, 단축법, 오브젝트가 카메라로부터 멀리 떨어질수록 작게 그리는 기법)이다. 이러한 효과는 관측 공간을 피라미드의 절두체로 정의할 때 나타난다.

2. 절두체를 정의하는 glFrustum() 커맨드는 먼저 원근 투영을 나타내는 행렬을 계산한 다음, 그 결과를 현재 투영 행렬과 곱한다.

3. glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);

a) 원근 형태의 절두체를 나타내는 행렬을 생성하고, 이를 형재 행렬과 곱한다. left, bottom, -near과 right, top, -near은 각각 카메라와 가까운곳에 위치한 클리핑 평면의 좌측 하단 모서리와 우측 상단 모서리 좌표를 나타낸다. near와 far인자는 각각 시점과 가까이 있는 클리핑 평면과 멀리 떨어진 클리핑 평면까지의 거리를 나타낸다.


b) gluPerspective(GLdouble 래표, GLdouble aspect, GLdouble near, GLdouble far);

(1) 원근 대칭 절두체를 나타내는 행렬을 생성하고 이를 현재 행렬과 곱한다. fovy 인자는 yz 평면상의 FOV각도를 나타내며, 이 값은 [0.0, 180.0]을 벗어날 수 없다. aspect 인자는 절두체의 종횡비를 나타낸다. near과 far는 각각 시점과 두 클리핑 평면 사이의 거리를 나타내며 반드시 양수로 지정되어야 한다.

C. 직교 투영(orthogonal projection)

1. 평행 육면체 모양의 관측 공간을 사용한다

2. 원근 투영과 달리 관측 공간의 양 끝면의 크기가 일정하기 때문에 카메라 거리가 오브젝트의 모양에 영향을 미치지 않는다

3. 건축 설계도나 CAD 설계등과 같이 오브젝트의 크기와 각도를 정확히 유지해야 하는 애플리케이션에서 주로 사용된다.

4. glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);

a) 직교 평행 관측 공간에 대한 행렬을 생성하고 이를 현재 행렬과 곱한다.


V. 뷰포트 변환

A. 뷰포트는 이미지가 그려질 윈도우의 직사각형 모양의 영역을 뜻한다

B. 윈도우 좌표로 측정됨, 이들은 윈도우의 좌측 하단 모서리를 기준으로 나타낸 스크린상의 픽셀 위치로 표현한다.

C. 관측공간을 벗어난 정점들은 모두 클리핑 된다.

D. 뷰포트 정의하기

1. 기본적으로 스크린 상에 윈도우를 생성하는 작업은 윈도우시스템이 담당한다

2. 그러나 기본적으로 윈도우를 처음 생성할 때 전체 윈도우에 해당하는 픽셀 영역을 뷰포트로 설정하도록 되어있다.

E. void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);

1. 최종 이미지가 매핑될 윈도우상의 픽셀 사각형을 정의한다.

2. (x,y)매개변수는 이러한 뷰포트의 좌측 하단 모서리를 나타낸다

3. width와 height는 뷰포트의 크기를 지정한다.

 

※ 다시한번 아래그림을 보면서 행렬과 좌표의 변화를 단계별로 생각해보자!


Reference :http://www.gisdeveloper.co.kr/opengl_tutorials/opengl15/15.htm

Sample Code

void init(void)

{

glClearColor(0.0, 0.0, 0.0, 0.0);

glShadeModel(GL_FLAT);

}

void display(void)

{

glClear(GL_COLOR_BUFFER_BIT);

glColor3f(1.0, 1.0, 1.0);

glLoadIdentity();

gluLookAT(0,0, 0.0, 5, 0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

glScalef(1.0, 2.0, 1.0);

glutWireCube(1.0):

glFlush();

}

  

void reshape(int w, int h)

{

glViewport(0, 0, (GLsizei) w, (GLsizei) h);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

glFrustum(-1.0, 1.0, -1, 0, 1.0, 1.5, 20.0);

glMatrixMode(GL_MODELVIEW);

}

  

int main(int argc, char** argv)

{

glutInit(&argc, argv);

glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB);

glutInitWindowSize(500,500);

glutInitWindowPosition(100,100);

  

gluitCreateWindow(argv[0]);

init();

glutDisplayFunc(display);

glutReshapeFunc(reshape);

glutMainLoop();

return 0;

}

int DrawGLScene(GLvoid)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glLoadIdentity(); //<1>

glTranslatef(0.0f, 0.0f, -22.0f); //<2>


glPushMatrix(); //<3>

glRotatef(o1_rot, 0.0f, 1.0f, 0.0f); //<4>

gluQuadricDrawStyle(obj, GLU_FILL); //<5>

glColor3f(0.9f, 0.9f, 0.9f); //<5>

gluSphere(obj, 2.0f, 24, 24); //<5>

gluQuadricDrawStyle(obj, GLU_LINE); //<5>

glColor3f(0.6f, 0.6f, 1.0f); //<5>

gluSphere(obj, 2.1f, 24, 24); //<5>

glPopMatrix(); //<6>


glPushMatrix(); //<7>

glRotatef(o2_rot1, 0.0f, 1.0f, 0.0f); //<8>

glTranslatef(distance, 0.0f, 0.0f); //<9>

glRotatef(o2_rot2, 0.0f, 1.0f, 0.0f); //<10>

gluQuadricDrawStyle(obj, GLU_FILL); //<11>

glColor3f(0.9f, 0.9f, 0.9f); //<11>

gluSphere(obj, 0.7f, 12, 12); //<11>

gluQuadricDrawStyle(obj, GLU_LINE); //<11>

glColor3f(1.0f, 0.6f, 0.6f); //<11>

gluSphere(obj, 0.75f, 12, 12); //<11>

glPopMatrix(); //<12>


o1_rot+=0.5f; if(o1_rot>359.5f) o1_rot=0.0f; //<13>

o2_rot1+=0.05f; if(o2_rot1>359.95f) o2_rot1=0.0; //<13>

o2_rot2+=2.0f; if(o2_rot2>358.0f) o2_rot2=0.0; //<13>


return TRUE;

}

i) <1>번 코드는 변환 행렬을 단위 행렬로 초기화 시켜주는 예이다.

ii) <2>번 코드는 이동 변환 행렬을 이용해서 좌표축을 Z축으로 -20만큼 이동한 것이다. 이렇게 이동한 이유는 우리가 OpenGL에서 물체를 바로보는 눈의 위치가 (0,0,0)에 있기 때문이다. 만약 이러한 이동이 없이 물체를 그려준다면 물체의 위치와 눈의 위치가 같게 되어서 물체가 보이지 않으므로 물체를 뒤쪽으로 이동시켜야 그려줌으로써 물체가 잘 보이도록 해주는 것이다.

iii) <3>번 코드에서 <6>번 코드까지 지구를 그려주는 것이다. 지구는 제자리에서 자전만 한다.

iv) <3>번 코드는 지금까지 변환행렬에 의해서 변환된 좌표축을 저장해 놓는 것이다.

v) <4>번 코드는 y축을 기준으로해서 회전을 o1_rot 각 만큼 회전을 시키는 것이다. 결과적으로 <2>번 코드에서의 이동 변환 행렬과 <4>번 코드의 회전 변환 행렬의 연산으로 좌표축이 변경될 것임을 알수있다.

vi) <5>번 코드들은 지구를 그려주는 코드들이다. (자세한 설명은 Quadric 객체를 설명한 장을 참고하기 바란다)

vii) <6>번 코드는 <3>번 코드에서 저장해둔 좌표축을 꺼내는 것인데 이렇게 함으로써 <4>번 코드에 의해 변환되기 이전의 좌표축으로 되돌릴수있다.

viii) <7>부터 <12>번까지는 달을 그려주는 것이다. 달은 지구를 중심으로 공전하며 또 스스로 자전한다.

ix) <7>번 코드 다시 변환행렬을 스택에 저장한다.

x) <8>번 코드는 y축을 기준으로 회전을 o2_rot1 각 만큼 하게 된다.

xi) <9>번 코드는 x축으로 distance만큼 이동을 하게된다. 결과적으로 <8>과 <9>번 코드에 의해서 달은 지구를 중심으로 공전을 하게되는 것이다.

xii) <10>번 코드는 y축을 기준으로 o2_rot2 각 만큼 회전하게 된다. 이 코드로써 달이 자전 하게 된다.

xiii) <8>, <9>, <10>번의 변환행렬의 순서는 중요하다. 순서가 바뀔 경우 우리가 원하는 동작과 위치는 잘못될 것이다.

xiv) <11>번 코드는 달을 그려주는 코드들이다. (자세한 설명은 Quadric 객체를 설명한 장을 참고하기 바란다)

xv) <12>번 코드는 다시 <7>번 코드에 의해서 저장된 변환 행렬을 꺼내는 것이다.

xvi) <13>번 코드들은 지구와 달의 회전각들을 일정하게 증가시켜주는 코드들이다.

xvii) 이렇게 정리를 하면 되겠다. 물체 마다 하나씩의 지역 좌표계를 둔다는 것이다. 즉 PushMatrix와 PopMatrix 사이에서 물체의 변환 행렬을 정의해주면 다른 물체에는 전혀 영향을 받지 않으므로 각 물체마다 독립적으로 생각해줄 수 있다.

 

 

 

 

 

 

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

Texture Mapping  (0) 2009.04.29
옥트리(octree)  (0) 2009.04.25
OpenGL 기초  (1) 2009.04.23
Picking 사용하기  (0) 2009.04.22
void glutIdleFunc(void (*func)(void))  (0) 2009.04.22
Posted by 마블(이환문)