Search

Output an Image

Created
2021/03/24
Tags
Chapter 2

1. The PPM Image Format

Renderer를 시작할 때마다 Image를 보기 위해선 특정 방법이 필요하다. Image를 보기 위한 가장 간단한 방법은 파일에 Write하는 것이다. Image를 보기 위한 Write 방식에는 많은 Format들이 있다. 대체적으로 이런 Format들은 복잡하게 구성이 되는데, 피터 교수는 그 중에서도 Plain TextPPM Image Format으로 Write하는 방식을 이용한다고 한다.
PPM (Portable Pix Map) PPMRGB 색상을 갖고 잇는 Image File Format 중 하나로써 각 라인의 끝에는 개행 문자가 존재하는 형식이다. PPM 형식은 아래의 예처럼 이용된다. # Header P3 3 2 255 # Data 255 0 0 # red 0 255 0 # green 0 0 255 # blue 255 255 0 # yellow 255 255 255 # white 0 0 0 # black PPMHeaderData로 구분된다. HeaderP3RGB 색상을 ASCII로 나타냈다는 것을 의미한다. 또한 두 번째 줄의 3Width Pixel을, 2Heigth Pixel을 의미한다. 마지막 줄의 255는 색상 하나가 가질 수 있는 최대값을 의미한다. PPM Image Format은 압축이 되지 않기 때문에 압축 형식보다 더 많은 공간과 대역폭을 필요로한다. 예를 들어, 192 by 128PNG File이 있다고 했을 때 이 File의 크기는 166 Byte가 된다. 하지만 192 by 128PPM File73848 Byte가 된다. 이러한 특성 때문에 PPM Image Format은 일반적으로 Image 작업의 중간 단계에서 사용된다. 만일 전송 중간 단계에서 정보의 손실이 없다고 한다면 PNG Image Format과 같이 조금 더 효율적인 Format으로 변환된다.
Image File Format Image File FormatImage를 디지털로 구성하고 저장하는 표준화된 방법을 의미하는데, 이 형식에 따라서 실제 데이터를 압축되지 않은 형식, 압축된 형식, 벡터 형식 등으로 저장할 수 있다. 형식에 맞게 갖춰진 Image 파일은 컴퓨터 디스플레이 혹은 프린터에서 파일 내의 데이터를 Rastering하여 사용하게 된다. Rastering을 하는 것을 Rasterization이라 하는데, 이는 Image의 데이터를 Pixel Grid로 변환하는 것을 의미한다. 각 Pixel은 색상을 지정할 수 있도록 여러 비트들을 갖고 있는데, 특정 장치에서 Rasterization을 하게 되면 장치 내의 bits_per_pixel을 읽어 Pixel을 올바르게 처리하게 된다.

2. Creating File

위에서 PPM Image Format을 배웠으므로, PPM Image Format에 해당하는 File을 생성해보자.
#include <iostream> int main(void) { // Image const int image_width = 256; const int image_height = 256; // Render std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n"; for (int j = image_height - 1; j >= 0; --j) { for (int i = 0; i < image_width; ++i) { auto r = double(i) / (image_width - 1); auto g = double(j) / (image_height - 1); auto b = 0.25; int ir = static_cast<int>(255.999 * r); int ig = static_cast<int>(255.999 * g); int ib = static_cast<int>(255.999 * b); std::cout << ir << ' ' << ig << ' '<< ib << '\n'; } } return (0); }
C++
1.
Pixel은 한 행으로 구분이 되며 좌에서 우로 읽으면 된다.
2.
한 행은 위에서 아래로 작성이 되었다.
3.
Convention에 따르면 R, G, B 값은 0.0에서 1.0의 범위를 갖는다. 따라서 차후에 High Dynamic Range를 이용할 때는 문제가 없겠지만, 그 전에는 위 코드에 따라 출력 구문을 수행하기 전에 RGB값을 0.01.0 사이의 값으로 Normalize하여 Mapping 시켜 사용한다. 따라서 위 코드는 바뀔 필요가 없다.
4.
R은 왼쪽에서 오른쪽으로 갈수록 밝아지고, G는 아래에서 위로 갈수록 밝아진다. R과 G가 함께 있으면 Yellow가 되기 때문에 우측 대각 모서리가 Yellow가 된다.
이에 따라 (0,0)(0, 0)지점이 어디를 의미하는지 잘 생각해보길 바란다.
위와 같이 PPM Image Format을 준수하는 Fileppm 확장자를 가진 File로 만드는 것은 그리 어려운 일이 아니다. 단순히 Redirecting만 해준다면 Image File이 된다. 터미널에서 > 라는 stdin을 이용하도록 하자.
위와 같은 명령어를 이용하면 아래와 같이 Image File이 실행되어 정상적인 결과를 확인할 수 있다.
PPM Image Format에서 배운 것처럼 위 image.ppm이라는 Filevim에서 읽으면 아래와 같이 되어 있는 것을 확인할 수 있다.
P3 256 256 255 0 255 63 1 255 63 2 255 63 3 255 63 4 255 63 5 255 63 6 255 63 ...(생략)
Plain Text

3. Adding a Progress Indicator

다음 Chapter를 진행하기 전에 결과물에 Progress Indicator를 적용해보자. 이렇게 하면 긴 Rendering에 대해서 손쉽게 진척도에 대해 추적할 수 있다. Progress Indicator는 진척도에 대한 추적 뿐 아니라, 만일 무한 루프나 다른 문제가 생겼을 때도 이를 파악하는데 유용한 방법이 될 수 있다.
Image File에 대한 출력은 std::cout이라는 표준 출력 (stdout)으로 이뤄지기 때문에, 이 내용은 그대로 두고 std:cerr라는 표준 에러 (stderr)를 이용하여 작성한다.
기존 코드에서 아래와 같이 변경된다.
#include <iostream> int main(void) { // Image const int image_width = 256; const int image_height = 256; // Render std::cout << "P3\n" << image_width << ' ' << image_height << "\n255\n"; for (int j = image_height - 1; j >= 0; --j) { std::cerr << "\rScanlines Remaining: " << j << ' ' << std::flush; for (int i = 0; i < image_width; ++i) { auto r = double(i) / (image_width - 1); auto g = double(j) / (image_height - 1); auto b = 0.25; int ir = static_cast<int>(255.999 * r); int ig = static_cast<int>(255.999 * g); int ib = static_cast<int>(255.999 * b); std::cout << ir << ' ' << ig << ' '<< ib << '\n'; } } std::cerr << "\nDone.\n"; return (0); }
C++
위 코드로 실행을 해보면 아래와 같이 진척도에 대해 추적이 가능한 것을 볼 수 있다. (위 코드에서의 표준 출력만을 표준 입력으로 사용했기 때문에 표준 에러만 콘솔에 출력이 되는 것을 볼 수 있다.)