이 글은 홍정모의 그래픽스 새싹코스 파트 1을 보고 정리했습니다.
honglab
All Courses, Graphics Introduction to Computer Graphics with DirectX 11 - Part 2. Realtime Pipeline
honglab.co.kr
이미지 읽고 저장하기
이미지 처리를 위해서는 이미지 파일을 읽어 오거나, 처리가 끝난 이미지를 특정 포맷(.ppm, .png, .jpg...) 등으로 저장할 수 있어야 한다.
하지만 C++은 일반적인 방법으로는 이미지를 읽어오거나 처리할 수 없다.
C++ 언어는 그래픽 출력보다는 컴퓨터 하드웨어 조작, 데이터 처리, 논리 연산을 위해 설계되었기 때문에 이러한 이미지를 직접 출력하는 기능을 지원하지 않는다.
이전에 c++로 PPM 파일을 출력한 경험이 있어 추가로 찾아봤는데
이미지 파일에 대한 2가지 다른 방식을 볼 수 있었다.
Binary Image Format(이진 이미지 형식)
- PNG, BMP, GIF같은 형식이 여기에 해당한다.
- 실제로 볼 때는 일반 이미지로 나타나지만 해당 데이터는 사람이 읽을 수 없는 이진 형식인 0과 1로 저장된다.
- PNG 파일 이미지를 보면 이진 데이터는 인간이 읽을 수 없고 전문적인 도구나 소프트웨어 없이는 내용 번역이 어렵다.
Text-based Image Format(텍스트 기반 이미지 형식)
- PPM, SVG 같은 형식이 여기에 해당한다.
- 사람이 쉽게 편집하고 이해할 수 있는 형식으로 저장된 이미지 데이터를 말한다.
- 텍스트 기반 형식은 ASCII 또는 Unicode 문자 인코딩을 사용하여 이미지 데이터를 나타낸다.
- PPM이미지 파일 예시를 보면 사람이 이해하고 편집하기 쉽게 픽셀마다 R, G, B 값이 텍스트파일로 저장되어 있는 것을 볼 수 있다.
- 위의 텍스트 기반 이미지 형식은 C++의 기본적인 파일 입출력 기능만 가지고도 출력할 수 있다.
- 이진 이미지 형식은 c++만으로는 안되고 외부 라이브러리를 사용하면 같은 역할을 수행할 수 있다.
강의에서는 jpg파일을 사용하였으므로 외부 라이브러리인 stb_image 라이브러리를 설치하여 진행했다.
헤더
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
- 헤더는 이렇게 include 하면 된다고 한다
- define을 하지 않으면 에러가 난다고 함.
이미지 읽기
unsigned char* img = stbi_load(filename, &width, &height, &channels, 0)
- stbi_load(파일이름, 너비, 높이, 채널값, 0)으로 이미지 파일 데이터를 unsigned char * 배열에 읽어올 수 있다.
- width, height, channels에 &(ref)가 붙어 있는 것으로 보아 file을 읽어올 때 알아서 값을 가져와 저장한다.
...
for (int i = 0; i < width * height; i ++)
{
pixels[i].v[0] = img[i * channels] / 255.0f;
pixels[i].v[1] = img[i * channels +1] / 255.0f;
pixels[i].v[2] = img[i * channels +2] / 255.0f;
pixels[i].v[3] = 1.0f;
}
...
- 불러온 값은 PNG같이 RGBA인 경우 4bytes, jpg같은 RGB의 경우에는 3bytes로 한 픽셀을 가져온다.
- 이 코드에서는 jpg값을 불러오기 때문에 i * channels+ 0, 1, 2의 형태로 순서대로 RGB값을 불러와 배열에 저장하고 있다.
이미지 쓰기
이미지 저장은 위의 읽기의 역순으로 진행한다.
std::vector<unsigned char> img(width * height * channels, 0);
for (int i = 0; i < width * height; i++)
{
img[i * channels] = uint8_t(pixels[i].v[0] * 255.0f); // v[0]이 0.0f 이상 1.0f 이하 가정
img[i * channels + 1] = uint8_t(pixels[i].v[1] * 255.0f);
img[i * channels + 2] = uint8_t(pixels[i].v[2] * 255.0f);
}
- pixels 배열에 저장한 값을 다시 unsigned char 배열에 저장하여 이미지 파일 데이터를 생성한다.
stbi_write_png(filename, width, height, channels, img.data(), width * channels)
- 차례대로 (너비, 높이, 채널, RGB 포맷으로 된 1차원 이미지 파일 데이터, 두 연속되는 행 사이의 바이트 수)를 인자로 받는다.
기초적인 이미지 처리
밝기와 어둡기를 조절하기 위해 각각의 RGB 값에 이런 코드를 써주면 된다.
for (int i = 0; i < image.height * image.width; i++)
{
image.pixels[i].v[0] = image.pixels[i].v[0] * 1.2f;
image.pixels[i].v[1] = image.pixels[i].v[1] * 1.2f;
image.pixels[i].v[2] = image.pixels[i].v[2] * 1.2f;
}
밝은 쪽에는 1.2정도의 값
어두운쪽에는 0.8 정도의 값을 곱해주었다.
밝은 쪽 부분이 색이 이상하게 나타나는데
이미지를 불러올 때 0.0과 1.0사이로 RGB값의 범위를 제한했는데 1.2라는 가중치를 곱하는 과정에서 해당 픽셀의 색상 값이 1.0을 넘어갔기 때문이다.
이를 해결하기 위해 사용하는 함수가 있다.
std::clamp()
값을 특정 범위 내에 가둬주는 함수로 이 함수를 이용하면 아무리 큰 값을 곱해도 RGB값의 범위를 0.0~1.0으로 제한할 수 있다.
이를 통해 이미지를 밝게 하는 코드를 고치면 (1.2로는 밝아진 티가 잘 안나서 좀 더 큰 값을 곱함)
for (int i = 0; i < image.height * image.width; i++)
{
image.pixels[i].v[0] = std::clamp(image.pixels[i].v[0] * 1.5f,0.0f,1.0f);
image.pixels[i].v[1] = std::clamp(image.pixels[i].v[1] * 1.5f, 0.0f, 1.0f);
image.pixels[i].v[2] = std::clamp(image.pixels[i].v[2] * 1.5f, 0.0f, 1.0f);
}
훨씬 밝아진 이미지
를 볼 수 있다.
매 프레임마다 호출되는 Update() 함수에 이런 형태의 코드를 넣으면 서서히 밝아지는 효과를 연출 할 수 있다.
그러나 잘 보면 움짤에서 색이 하얗게 변하지 않는 부분이 보이는데 이 부분 픽셀 R,G,B 요소 중 특정 값이 0.0f인 값이 있어 어떤 값을 곱해도 0.0f 가 나오기 때문이다.
for (int i = 0; i < image.height * image.width; i++)
{
image.pixels[i].v[0] = std::clamp(image.pixels[i].v[0] + 0.01f, 0.0f, 1.0f);
image.pixels[i].v[1] = std::clamp(image.pixels[i].v[1] + 0.01f, 0.0f, 1.0f);
image.pixels[i].v[2] = std::clamp(image.pixels[i].v[2] + 0.01f, 0.0f, 1.0f);
}
이 문제를 해결하기 위해서는 initialize 시에 모든 픽셀에 0.01f와 같은 변화를 거의 느낄 수 없을 만큼의 작은 값을 각 픽셀의 RGB값에 더하는 것으로 해결할 수 있다.
결과 이미지
전체적으로 화면이 하얗게 바뀌는 것을 볼 수있다.
이외에도 반전을 주는 등 다른 효과도 처리할 수 있다.
강의에서 배운 것
- stb_image 라이브러리를 통한 이미지 입출력
- 범위를 벗어나 버리는 색깔에 대해 std::clamp()함수를 통한 값 제한
- 파일로부터 읽어온 RGB 값을 단순 연산을 통해 이미지 처리하는 법
'Graphics > Directx11' 카테고리의 다른 글
Fbx SDK Import 시의 Polygon을 삼각형으로 (0) | 2023.08.17 |
---|---|
그래픽스 새싹코스 파트1 - 가우시안 블러와 블룸 효과 (0) | 2023.03.27 |