민돌이2 2022. 1. 20. 21:31

컴퓨터 모니터는 각 픽셀마다 빨간색(R), 녹색(G), 파란색(G) 빛을 섞어서 방출한다. 모니터가 방출하는 적, 녹, 청색광의 세기는 상한과 하한이 존재한다. 빛의 세기를 나타낼 때에는 0에서 1까지 정규화된 값을 사용하는 것이 편리하다. 0은 빛이 전혀 없는 것이고 1은 빛의 세기가 최대인 값이다. 예를 들어 (0.25, 0.67, 1.0)은 25% 세기의 적색광 + 67% 세기의 녹색광 + 100% 세기의 청색광의 혼합을 뜻한다.

 

 

1.색상 연산

한 색상에서 다른 색상을 더하거나 빼서 새 색상을 얻는 것이 가능하고, 스칼라 곱셈도 가능하다.

 

색상 벡터만의 연산으로 변조(modulation)라고도 하는 성분별 곱셉(componentwise multiplication)이 있다.

성분별 곱셉은 주로 조명 공식에 쓰인다. 여기서 알고 가야할 것은 빛은 합칠수록 어두워진다는 점이다. 수치적으로 생각을 해봐도 0 ~ 1사이의 값은 곱할 수록 값은 감소한다는 점이다. 수학의 아름다움인지 몰라도 현실세계와 맞물린다.

색상 값은 0 ~ 1사이의 값임을 기억하고 있어야 한다. 1.0초과의 색도 1.0으로 나타내야하고 0.0미만의 즉 음수의 색도 0.0으로 나타내야한다. 색상을 연산하다보면 1.0을 초과하거나 0.0미만인 경우 계산이 꼬일 수 있으므로 한정(clamping) 연산을 적용해야 한다.

내적이나 외적 같은 연산은 색상 벡터에 대해서는 아무런 효과가 없다. 

 

 

2.128비트 색상

적, 녹, 청 외에 알파라고 부르는 색상 성분 하나를 색상 벡터에 추가해서 사용할 수 있다. 알파 성분은 주로 색상의 불투명돌르 나타낸다. 즉, 하나의 색상을 4차원 벡터 (r, g, b, a)로 표현한다는 뜻이다. 그리고 각 성분을 32비트 부동소수점 값으로 표현함으로써 하나의 색상을 128bit로 표현한다.

수학적으로 색상은 4차원 벡터이므로, 코드에서도 그냥 XMVECTOR 형식을 이용하여 색상을 표현하면 된다. XNA Math 라이브러리의 벡터 함수들로 색상 연산을 수행할 때 SIMD의 이득을 취할 수 있다.

XMVECTOR XMColorModulate(FXMVECTOR C1, FXMVECTOR C2);

 

 

 

3.32비트 색상

각 성분당 1byte(8bit)를 할당해서 하나의 색상을 32bit로 표현할 수도 있다. 성분당 8bit이므로 한 성분에 대한 총 256가지의 세기를 표현할 수 있는데, 0은 해당 성분이 전혀 없는 것이고 255는 세기가 최대를 의미한다.

XNA Math 라이브러리는 32bit 색상의 저장을 위해 아래와 같은 구조체를 제공한다.

typedef struct _XMCOLOR
{
	union
	{
		struct
		{
			UINT b : 8;
			UINT g : 8;
			UINT b : 8;
			UINT a : 8;
		};
		UINT c;
	};

#ifdef __cplusplus
	_XMCOLOR() {};
	_XMCOLOR(UINT Color) : c(Color) {};
	_XMCOLOR(FLOAT _r, FLOAT _g, FLOAT _b, FLOAT _a);
	_XMCOLOR(CONST FLOAT* pArray);
	
	operator UINT() { return c; }

	_XMCOLOR& operator=(CONST _XMCOLOR& Color);
	_XMCOLOR& operator=(CONST UINT Color);
#endif
} XMCOLOR;

정수 구간 [0, 255]를 실수 값 구간 [0, 1]로 사상함으로써 32bit 색상을 128bit로 변환하는 것이 가능하다. 각 성분을 255로 나누면 된다.

예를 들어 32bit 색상 (80, 140, 200, 255)는

반대로 128bit 색상을 32bit 색상으로 변환할 때는 각 성분에 255를 곱하고 올림하면 된다.

 

32bit 색상은 네 개의 8bit 색상 성분을 각각 갖고 있는게 아니라 32bit 정수값에 채워넣어 한개를 갖고 있는 형태이다. 그런 32bit 색상을 128bit색상으로 변환하려면 추가적인 비트 연산들이 필요하다. XMCOLOR도 이 경우에 해당한다. XNA Math 라이브러리는 XMCOLOR 하나를 받아서 XMVECTOR를 돌려주는 함수를 제공한다.

XMVECTOR XMLoadColor(CONST XMCOLOR* pSource);

XMCOLOR 클래스는 ARGB 배치를 사용한다. XNA Math 라이브러리는 XMVECTOR 색상을 XMCOLOR로 변환하는 함수도 제공한다.

VOID XMStoreColor(XMCOLOR* pDestination, FXMVECTOR V);

 

색상 성분들을 채워 넣는 방식은 여러가지가 있다. RGBA순서도 있고 ARGB, ABGR로 채워 넣을 수 있다. 

내가 지형의 높이맵을 이미지로 뽑아내서 저장할 때 고생했던 부분이다. 보통 RGBA이라고 생각하는데 ARGB순서여서 고생했었다. 그래서 색상 저장하는 함수에서 비트연산하여 분리하는 코드를 구현했었다.

for (UINT z = 0; z < destDesc.Height; z++)
{
	for (UINT x = 0; x < destDesc.Width; x++)
	{
		UINT index = destDesc.Width * z + x;

		UINT a = 0;
		if (pixels[index] >= 0.0f)
			a = (255 << 24);
		
		descPixels[index] = a + pixels[index];
	}
}

 

일반적으로 128bit 색상은 다수의 색상 연산들이 진행되는 곳에 쓰인다. 128bit 색상은 정밀도를 위한 bit가 많으므로 산술 오차가 과도하게 누적되는 일이 없다. 그러나 최종적인 픽셀 색상은 일반적인 후면 버퍼에 32bit 색상으로 저장된다. 현재 물리적 디스플레이 장치들은 고해상도 색상의 장점을 취하지 못한다.

728x90