각 플랫폼이 사용하는 하드웨어와 렌더링 라이브러리의 차이로 인해 다양한 픽셀 포맷 *(1) 이 존재한다. 예를 들어, DirectX에서는 DXGI_FORMAT 포맷을 사용하고 OpenGL에서는 GL_RGBA, GL_RGB 같은 포맷을 많이 사용.
일반적으로 픽셀 포맷은 채널 수(예: RGB, RGBA), 각 채널의 비트 수, 정수/부동소수점 여부 등에 따라 달라진다. 다양한 포맷들로 인해 각 장치나 API에서 다르게 처리되기 때문에, 개발할 때 항상 호환성을 고려해야 한다. 이를 위해서는 대표적으로 호환성 라이브러리 사용 및 데이터 변환 *(2) 및 튜닝과 정규화된 *(3) 데이터 사용등 이 있다
그래픽스 리소스를 로드할 때 포맷을 변환하는 과정은 보통 다음과 같다.
1. 원하는 리소스를 파일 시스템에서 읽어 메모리에 로드.
2. 비트맵 데이터*(4) 를 얻기 위해 로드한 파일 형식을 디코딩
3. 디코딩을 통해 얻은 비트맵 데이터를 원하는 포맷으로 변환
4. 색상 정보를 적절하게 매핑하거나 비트 수를 줄이는 변환 알고리즘등을 원하는대로 사용*(5)
*(1) 다양한 픽셀 포맷:
32비트 포맷 (8비트 RGBA): 각 색상 채널(Red, Green, Blue)과 알파(Alpha) 채널이 각각 8비트씩 할당된 포맷. 총 32비트(4바이트)를 사용하며 한 픽셀이 0에서 255까지의 값을 가진 4개의 채널을 포함. 일반적으로 많이 사용되고, 대부분 플랫폼에서 지원. 색상이 풍부하고, 알파 채널로 투명도를 지원해서 레이어드 렌더링이나 블렌딩 같은 효과를 구현할 수 있다.
16비트 포맷( RGB565 포맷 ): 알파 채널이 없고, RGB 색상 정보만 저장. 총 16비트(2바이트) 를 사용. Green에만 6비트를 할당하는 이유는 인간의 시각이 중간 파장대인 녹색 영역에 가장 민감하게 반응하기 때문.그래픽 성능이나 메모리 효율이 중요한 모바일 기기나 임베디드 장치에서 많이 사용.
HDR ( High Dynamic Range )포맷: 고품질 그래픽을 위해 부동소수점 포맷을 사용. RGBA16F: 각 채널이 16비트 부동소수점(float)으로 표현. 총 64비트(4채널 × 16비트). RGBA32F: 각 채널이 32비트 부동소수점으로 표현. 총 128비트(4채널 × 32비트). 일반적으로 SDR(Standard Dynamic Range)에서는 8비트 또는 16비트 정수형으로 채널 값을 표현해서 밝기와 색상을 한정된 범위 내에서 표현하지만, HDR 포맷은 보통 부동소수점(float) 값을 사용해 훨씬 더 넓은 범위의 밝기와 색상을 표현할 수 있다.
*(2). 데이터 포맷 변환 : 예를 들어, 32비트 RGBA에서 16비트 RGB565로 변환
uint16_t ConvertToRGB565(uint8_t r, uint8_t g, uint8_t b)
{
uint16_t rgb565 = (r >> 3) << 11 | (g >> 2) << 5 | (b >> 3);
return rgb565;
}
uint16_t ConvertToRGB565(uint8_t r, uint8_t g, uint8_t b)
{
uint16_t r565 = (r >> 3) & 0x1F; // 5비트로 조정
uint16_t g565 = (g >> 2) & 0x3F; // 6비트로 조정
uint16_t b565 = (b >> 3) & 0x1F; // 5비트로 조정
return (r565 << 11) | (g565 << 5) | b565;
}
//비트 시프트를 사용하여, 8비트 색상 채널을 5비트로 변환
uint8_t r_8bit = 255; // 8비트 색상 값
uint8_t r_5bit = r_8bit >> 3; // 5비트로 변환 (3비트 시프트)
*(3). 색상 정보 정규화(Normalization) : 이미지의 색상 값이 0에서 255 범위에 있을 때, 이를 0에서 1 사이로 정규화하면 다양한 포맷 간 변환이 용이해진다. 이렇게 정규화한 값을 사용해 부동소수점 색상으로 변환하거나 다른 포맷으로 변환할 때 활용할 수 있다.
float r_normalized = r / 255.0f;
float g_normalized = g / 255.0f;
float b_normalized = b / 255.0f;
*(4) 비트맵 데이터 : 비트맵 데이터는 일반적으로 픽셀 배열 형태로 존재하며, 색상 채널(RGB, RGBA 등)과 비트 깊이(8비트, 16비트 등)이 있다.
*(6) 색상 정보 매핑 예시
- 자연스러운 색상 표현을 위한 감마보정 (Gamma Correction)
float GammaCorrect(float value, float gamma)
{
return pow(value, 1.0f / gamma);
}
// 예를 들어 R, G, B 값에 대해 감마 보정을 적용할 수 있다.
//대부분의 디스플레이는 감마가 2.2에 가까우므로, 이를 고려하여 색상 값을 조정
float r_corrected = GammaCorrect(r_normalized, 2.2f);
float g_corrected = GammaCorrect(g_normalized, 2.2f);
float b_corrected = GammaCorrect(b_normalized, 2.2f);
- 색상 공간 변환 ( RGB에서 HSV(색상, 채도, 명도)로 변환하면 색상 정보를 다르게 표현할 수 있다).
void RGBToHSV(uint8_t r, uint8_t g, uint8_t b, float& h, float& s, float& v)
{
float r_norm = r / 255.0f;
float g_norm = g / 255.0f;
float b_norm = b / 255.0f;
float max = std::max(r_norm, std::max(g_norm, b_norm));
float min = std::min(r_norm, std::min(g_norm, b_norm));
v = max;
float delta = max - min;
if (max != 0)
{
s = delta / max;
} else
{
s = 0;
h = -1; // undefined
return;
}
if (r_norm == max)
{
h = (g_norm - b_norm) / delta; // between yellow & magenta
}
else if (g_norm == max)
{
h = 2 + (b_norm - r_norm) / delta; // between cyan & yellow
}
else
{
h = 4 + (r_norm - g_norm) / delta; // between magenta & cyan
}
h *= 60; // degrees
if (h < 0) {
h += 360;
}
}
- 밝기 값을 비트 수를 줄이면서 조정하는 감마 압축(Gamma Compression) : 밝기 값의 감마를 적용하여 더 많은 정보가 밝은 영역에 집중되도록 할 수 있다. 이미지에서 세부 정보를 보존하는 데 효과적
uint8_t ApplyGammaCorrection(uint8_t value, float gamma)
{
float normalized = value / 255.0f;
float corrected = pow(normalized, gamma);
return static_cast<uint8_t>(corrected * 255);
}
- 변환된 색상 값을 반올림하여 정확도 조정을 통해 비트 수 줄이기 (Color Rounding)
//RGB에서 5비트로 변환
uint8_t r_8bit = 200; // 8비트 색상 값
uint8_t r_5bit = (r_8bit + 4) / 8; // 5비트로 변환 (반올림)
- Floyd-Steinberg Dithering은 색상 오류를 주변 픽셀에 분산시켜서 디더링하는 방법으로, 더 세밀한 결과를 얻을 수 있다
void FloydSteinbergDither(uint8_t* image, int width, int height, const uint8_t* palette, int paletteSize) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
// 현재 픽셀의 색상 추출
uint8_t r = image[(y * width + x) * 3 + 0]; // Red
uint8_t g = image[(y * width + x) * 3 + 1]; // Green
uint8_t b = image[(y * width + x) * 3 + 2]; // Blue
// 가장 가까운 색상 찾기 (여기서는 단순한 예시로)
uint8_t closestColor = FindClosestColor(r, g, b, palette, paletteSize);
// 오류 계산
int errorR = r - closestColor.r;
int errorG = g - closestColor.g;
int errorB = b - closestColor.b;
// 다음 픽셀에 오류 확산
if (x + 1 < width) {
image[(y * width + x + 1) * 3 + 0] += errorR * 7 / 16;
image[(y * width + x + 1) * 3 + 1] += errorG * 7 / 16;
image[(y * width + x + 1) * 3 + 2] += errorB * 7 / 16;
}
if (x - 1 >= 0 && y + 1 < height) {
image[((y + 1) * width + x - 1) * 3 + 0] += errorR * 3 / 16;
image[((y + 1) * width + x - 1) * 3 + 1] += errorG * 3 / 16;
image[((y + 1) * width + x - 1) * 3 + 2] += errorB * 3 / 16;
}
if (y + 1 < height) {
image[((y + 1) * width + x) * 3 + 0] += errorR * 5 / 16;
image[((y + 1) * width + x) * 3 + 1] += errorG * 5 / 16;
image[((y + 1) * width + x) * 3 + 2] += errorB * 5 / 16;
}
if (x + 1 < width && y + 1 < height) {
image[((y + 1) * width + x + 1) * 3 + 0] += errorR * 1 / 16;
image[((y + 1) * width + x + 1) * 3 + 1] += errorG * 1 / 16;
image[((y + 1) * width + x + 1) * 3 + 2] += errorB * 1 / 16;
}
}
}
}
- Ordered Dithering은 미리 정의된 매트릭스를 사용하여 픽셀의 밝기를 조정하는 방식으로, 구현이 간단하고 빠른 결과를 얻을 수 있다
const int ditherMatrix[4][4] = {
{0, 8, 2, 10},
{12, 4, 14, 6},
{3, 11, 1, 9},
{15, 7, 5, 13}
};
void OrderedDither(uint8_t* image, int width, int height, int threshold) {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixelIndex = (y * width + x) * 3; // RGB
uint8_t r = image[pixelIndex + 0];
uint8_t g = image[pixelIndex + 1];
uint8_t b = image[pixelIndex + 2];
// 밝기 계산 (여기서는 단순히 평균으로)
uint8_t brightness = (r + g + b) / 3;
// ditherMatrix를 이용한 변환
int ditherValue = ditherMatrix[y % 4][x % 4];
if (brightness < threshold + (ditherValue * 16) / 16) {
image[pixelIndex + 0] = image[pixelIndex + 1] = image[pixelIndex + 2] = 0; // 검은색으로
} else {
image[pixelIndex + 0] = image[pixelIndex + 1] = image[pixelIndex + 2] = 255; // 흰색으로
}
}
}
}
Drawcall 을 줄이기 위한 방법 (0) | 2024.11.01 |
---|---|
라이트와 그림자의 원리 (0) | 2024.11.01 |
Forward Rendering VS Deffered Rendering (0) | 2024.11.01 |
Geometric Objects (0) | 2023.07.27 |
Transformation (0) | 2023.07.27 |