상세 컨텐츠

본문 제목

Platform별 pixel format 차이점

Study/Graphics

by Arq.Dev5igner 2024. 11. 1. 13:16

본문

각 플랫폼이 사용하는 하드웨어와 렌더링 라이브러리의 차이로 인해 다양한 픽셀 포맷 *(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; // 흰색으로
            }
        }
    }
}

 

'Study > Graphics' 카테고리의 다른 글

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

관련글 더보기