home lecture link bbs blame

◈2D Graphic Programming◈


그래픽 프로그래밍은 2D 이미지에 대한 픽셀 프로세싱(Pixel Processing) 입니다. 픽셀
프로세싱은 알고리듬과 수학 등을 이용해서 주어진 픽셀을 처리함을 말합니다.
이 장에서는 여러 이미지 프로세싱 중에서 게임에서 간단히 적용할 수 있는 이미지 처리에
대해서 살펴 보도록 하겠으며 빠져있거나 설명이 미흡한 부분은 “영상 처리” 관련 서적을
참고 하기 바랍니다.
다음 이 강의에서 사용되는 텍스처, 스프라이트, 인풋 등이 포함된 배경 소스 파일입니다.

Ip00_Base.zip





1. 픽셀 기반 처리


1. 색상 버퍼


이미지 프로세싱을 하려면 DirectX의 텍스처에서 색상 버퍼를 가져와야 합니다. 색상 버퍼는
또한DirectX의 서피스(Surface)로 부르기도 합니다. 텍스처 객체의 멤버 함수 중에서
GetLevelDesc() 함수는 텍스처의 픽셀 정보를 가져오는 함수입니다.
함수의 인수로 D3DSURFACE_DESC 구조체를 사용하는데 이 구조체 안에는 색상 버퍼의 너비와
높이 등등이 저장되어 있습니다.

이렇게 픽셀에 대한 정보를 얻어온 후에 픽셀의 색상 버퍼를 가져와야 하는데 LockRect()
함수를 호출 하면 색상 버퍼를 가져올 수 있습니다. 함수의 인수로 D3DLOCKED_RECT 구조체를
사용하는데 이 구조체 안에 색상버퍼의 시작 포인터와 Pitch값이 저장되어 있습니다.

이 Pitch 값을 폭으로 나누면 색상에 대한 바이트를 얻게 됩니다. 이 바이트 값을 가지고
시작 포인터를 DWORD(unsigned long int: 32비트), WORD(unsigned short int: 16비트),
BYTE(unsigned char: 8비트) 형으로 캐스팅 해서 사용해야 합니다.
	LPDIRECT3DTEXTURE9	pTx;
	D3DSURFACE_DESC		dsc;
	D3DLOCKED_RECT		rc;

	INT		Pitch = 0;
	INT		nByte = 0;

	pTx->GetLevelDesc(0, &dsc);			// 텍스처 정보 얻기
	pTx->LockRect(0, &rc, NULL, 0);			// 텍스처 픽셀 정보 얻기
	INT	Pitch = rc.Pitch;
	nByte = Pitch/dsc.Width;			// 1픽셀당 바이트 계산

	if(4==nByte)					// 32Bit
	{
		DWORD*  pARGB = (DWORD*)rc.pBits;
		…
	}
	else if(2 == nByte)				// 16 Bit
	{
		WORD*  pARGB = (WORD*)rc.pBits;
		…
	}
	else if(1 == nByte)				// 8 Bit
	{
		BYTE*  pARGB = (BYTE*)rc.pBits;
		…
	}

	pTx->UnlockRect(0);





전체 코드는 다음 예제를 참고 하기 바랍니다.

Ip11_color.zip





2. Color Shift


이미지 프로세싱에서 가장 먼저 시작하는 것은 색상에 대한 이해입니다. 색상에 대한 표현은
여러 가지가 있는데 빛의 삼원색을 바탕으로 한 RGB(Red, Green, Blue), 인쇄에서 많이
사용되고 잉크를 기본으로 한 CMYK(Cyan, Magenta, Yellow, Black), 색상, 명도, 채도를
3차원으로 구성한 HIS(Hue, Intensity, Saturation) 등이 있습니다.

이 강의에서는 RGB를 기본으로 할 것입니다. 또한 대부분 색상을 [0, 255] 범위의 값을
사용하지만 이미지 프로세싱에서는 주로 [0, 1.0] 범위의 값을 주로 사용하는 데 이 범위의
색상 값을 사용하면 가산 연산을 덧셈으로, 감산 연산을 곱셈으로 처리할 수 있습니다.

DirectX는 색상을 각 비트당 8비트인 Alpha, Red, Green, Blue의 32비트 값을 사용합니다.
따라서 색상을 R, G, B를 각각 처리하는 과정이 있다면 최종 색상을 만드는 비트 연산이
필요합니다.물론 색을 분해하기 위해서도 반드시 비트 연산이 필요합니다.

비트 연산은 초보자에게는 수고가 많이 드는 일입니다. 다행히도 DirectX는 색상에 대해서
[0, 1.0] 범위를 가지고, float 형 Red, Green, Blue, Alpha의 값을 가지는 D3DXCOLOR
구조체가 있습니다.
이 구조체는 DWORD(unsigned long int: 32비트) 형 캐스팅 연산자가 다중 정의(Overloading)
되어 있어 색상을 쉽게 변환해 주고 각각의 Red, Green, Blue, Alpha에 대해서 몇 개의
편리한 연산자가 다중 정의되어 있어 사용하기 편리합니다.

또한 DirectX의 쉐이더에서 색상의 경우 이 구조체의 사용이 가장 좋으니 미리 익숙해
지는 것이 좋습니다.

다음의 예제는 DWORD형으로 되어 있는 픽셀에서 Red, Green, Blue를 각각 추출해서 렌더링
하는 예제입니다.
	void McColor_ColorRed(DWORD* pOut, DWORD* pIn, INT iImgW, INT iImgH)
	{
		…
		D3DXCOLOR xclr = pIn[nIdx];
		xclr.r *= 1.0f;
		xclr.g *= 0.0f;
		xclr.b *= 0.0f;
		pOut[nIdx] = xclr;
		…
	}

위의 코드를 보면 색상 값을 D3DXCOLOR 구조체 변수로 읽고, Red에 1.0을 곱하고 나머지는
0.0을 곱했습니다. 즉 Red Shift 연산을 의미합니다. 만약 Green Shift를 하려면
xclr.g *= 1.0f 을 하면 되고 Blue Shift 연산을 하려면 xclr.b *= 1.0f 를 한 다음
나머지는 0.0f를 곱하면 됩니다.






다음 예제는 Red Shift에 대한 예제이며, Green 과 Blue에 대해서는 각자 해보기 바랍니다.

Ip12_rgb.zip





3. 단색(Mono Color)


Color Shift를 할 수 있다면 단색을 만들어 낼 수 있습니다. 보통 흑백처리라면 다음과 같이
R, G, B값을 더한 후에 3으로 나눈 평균값을 사용할 수 있습니다.
	최종 색상 = (붉은색 + 초록색 + 파랑색) /3

하지만 이미지 프로세싱에서는 좀 더 사람의 눈에 가까운 현실적인 다음과 같은 공식을
사용합니다.
	최종 색상 = 붉은색 * 0.299 + 초록색 * 0.587 + 파랑색 * 0.114

이 공식이 현실적인 이유는 사람의 눈에서 민감한 정도가 초록색, 붉은색, 파랑색 순으로
되어 있고 그 민감도에 맞게 평균적인 값을 적용하기 때문입니다. 물론 이 공식도 밝기에
따라서 어느 정도 보정을 해서 사용해야 정확하지만 게임에서 사용하기에는 큰 무리가
없습니다.

앞서 보여준 Color Shift를 만드는 코드에서 다음과 같이 수정을 하면 단색을 표현
할 수 있습니다.
	void McColor_TransColorMono(DWORD* pOut, DWORD* pIn, INT iImgW, INT iImgH)
	…
		int nIdx = y*iImgW + x;
		D3DXCOLOR c = pIn[nIdx];

		//I = 0.30R + 0.59G + 0.11B
		float I = c.r * 0.3f + c.g * 0.59f + c.b * 0.11f;
		pOut[nIdx] = D3DXCOLOR(I, I, I, c.a);
	…
	}





Ip13_Mono.zip





4. 색상 반전(Inverse)


요즘은 Digital Camera가 보편화 되어 있어서 거의 필름은 사용하지 않습니다. 간혹 현상된
필름을 본 분이 있다면 밝은 부분이 어둡게 나오고, 어두운 부분이 밝게 보이며, 색상이
반대로 뒤집혀 있음을 본 기억이 있을 것입니다. 이것은 필름을 다시 인화지에 현상을
해야 하기 때문에 필름 색상을 반전 시켜 놓은 것입니다.

물리적인 필름의 색상 반전은 여러 가지 기술이 필요하지만 이미지 프로세싱에서는 간단히
처리할 수 있습니다. 다음 공식처럼 최대 값 1.0에서 각 색상 값을 뺄셈을 하면 됩니다.
	반전된 붉은색 = 1.0f - 붉은색
	반전된 초록색 = 1.0f - 초록색
	반전된 파랑색 = 1.0f - 파랑색

코드는 다음과 같습니다.
	void McColor_TransColorInverse(DWORD* pOut, DWORD* pIn, INT iImgW, INT iImgH)
	{
	…
		D3DXCOLOR xclr = pIn[nIdx];

		xclr.r = 1- xclr.r;
		xclr.g = 1- xclr.g;
		xclr.b = 1- xclr.b;
		pOut[nIdx] = xclr;
	…
	}





Ip14_Inverse.zip





5. Gamma


모니터 응용프로그램을 설치하면 대부분의 프로그램에서 감마 값 조절이라는 항목이
있습니다. 감마 값 조절은 색 온도인 색의 밝기에 영향을 주는데 수학적으로 다음과
같은 지수(exponential) 함수를 사용합니다.
	색상 강도(intensity) = x (1/r) , x=[0, 1.0]

공식을 보면 Gamma 값 r=0에 가까우면 x는 무한대의 승수가 곱해지므로 1을 제외한
모든 값은 0에 가까운 값이 되고 반대로 r이 무한히 커지면 0을 제외한 모든 색상 값이
1에 가까워지는 성질이 있습니다.

이 공식을 프로그램에 적용하기 위해서 수학 함수 중에서 승수를 계산할 수 있는
pow() 함수 또는 powf() 함수를 사용합니다.
	void McColor_TransColorGamma(DWORD* pOut, DWORD* pIn, INT iImgW, INT iImgH, D3DXCOLOR xGamma)
	{
	…
		D3DXCOLOR xclr = pIn[nIdx];

		xclr.r = powf(xclr.r, 1.0f/xGamma.r);
		xclr.g = powf(xclr.g, 1.0f/xGamma.g);
		xclr.b = powf(xclr.b, 1.0f/xGamma.b);

		pOut[nIdx] = xclr;
	…
	}





Ip15_Gamma.zip





6. Bit Planner Slicing


이미지 프로세싱에서 색상은 통상 byte(8비트)단위로 처리가 됩니다. 또한 색상을 비트
단위로 연산을 하게 되는 경우가 많은데 처리 시간을 빠르게 하기 위해 영상이 어느 정도
보존 될 때까지 레벨을 두어 비트 연산으로 해상도를 낮추는 비트 플래너 슬라이싱
(Bit Planner Slicing) 변환이라는 것이 있습니다. 여기서 레벨은 2의 승수로
정해집니다.
	void McColor_TransColorBitPlanner(DWORD* pOut, DWORD* pIn, INT iImgW, INT iImgH, INT nBit)
	{
	…
		D3DXCOLOR xclr = pIn[nIdx];
		DWORD r = DWORD(xclr.r * 255);
		DWORD g = DWORD(xclr.g * 255);
		DWORD b = DWORD(xclr.b * 255);

		r >>= (8-nBit);
		g >>= (8-nBit);
		b >>= (8-nBit);

		r < < = (8-nBit);
		g < < = (8-nBit);
		b < < = (8-nBit);

		pOut[nIdx] = D3DXCOLOR(r/255.f, g/255.f, b/255.f, 1.f);
	…
	}





Ip16_Bitplanner.zip





2. 픽셀 인접 기반 처리


앞서 보여준 픽셀 기반 처리과정은 하나의 픽셀을 가지고 처리하는 기술입니다. 인접기반
처리는 하나의 픽셀과 이웃하는 픽셀 들 사이에 비중 값을 두어서 처리하는 기술입니다.

인접 기반 처리는 연산을 빠르게 수행하기 위해서 인접 픽셀에 대해서 마스킹(Masking)
이라는 값을 사용합니다. 이 마스킹에 따라서 양각 효과(Embossing), 흐림 효과(Blurring),
흐림 효과와 반대 개념인 Sharpening, 끝 부분을 추출하는 윤곽선 추출(Edge) 등이
있습니다.

이들 마스킹은 임의로 만든 것이 아니라 수학 함수를 사용하거나 혹은 미분 방정식을
근사(Approximation) 해서 구한 결과 값들입니다.
마스킹을 구하는 방법은 여러 설명이 필요하니 이 부분은 다른 영상 처리 기술 서적들을
참고 하기 바라며 여기서는 마스킹의 값과 이들의 효과를 보여 드리겠습니다.





1. 양각 효과(Embossing)

	FLOAT	g_MaskSouthEast[][3] =
	{
		{  2,  1,  0},
		{  1,  0, -1},
		{  0, -1, -2},
	};





Ip21_Embossing.zip





2. 흐림 효과(Blurring)

	FLOAT	g_MaskBlurRect3X3[][3] =
	{
		{  1/9.f,  1/9.f,  1/9.f},
		{  1/9.f,  1/9.f,  1/9.f},
		{  1/9.f,  1/9.f,  1/9.f},
	};

	FLOAT	g_MaskBlurCircular5X5[][5] =
	{
		{    0.f,  1/21.f,  1/21.f,  1/21.f,     0.f},
		{ 1/21.f,  1/21.f,  1/21.f,  1/21.f,  1/21.f},
		{ 1/21.f,  1/21.f,  1/21.f,  1/21.f,  1/21.f},
		{ 1/21.f,  1/21.f,  1/21.f,  1/21.f,  1/21.f},
		{    0.f,  1/21.f,  1/21.f,  1/21.f,     0.f},
	};





Ip22_Blurring.zip





3. Sharpening

	FLOAT	g_MaskSharpening1[][3] =
	{
		{  1, -2,  1},
		{ -2,  5, -2},
		{  1, -2,  1},
	};

	FLOAT	g_MaskSharpening2[][3] =
	{
		{  0, -1,  0},
		{ -1,  5, -1},
		{  0, -1,  0},
	};





Ip23_Sharpening.zip

Copyright (c) 2004 3dapi.com All rights reserved.

Creative Commons License