PPM Image Format

레이트레이싱을 적용한 결과를 어떤 이미지를 통해 보아야하기 때문에,

먼저 어떤 이미지 형식을 사용하여 결과를 만들 것인지 선택해야 한다.

 

여기서는 PPM 형식사용하는데, PPM은 텍스트를 사용하여 이미지에 대한 정보를 저장하는 형식이다.

PPM형식의 예시

해당 형식을 사용하는 이유는, 압축이 필요 없으며, 몇 줄의 코드로 이미지 데이터를 입력할 수 있고,

수치가 이상하면 바로 눈에 보이기 때문에 디버깅이 보다 수월하다는 장점이 있다.

 

그래서 파일 포맷 구현을 빠르게 넘어가고, 렌더링 로직에 집중할 수 있다.

 

 

Generate Image

main.cpp

이미지의 크기를 설정한다.

int imageWidth = 256;
int imageHeight = 256;

 

그 후 렌더링을 수행한다.

파란색은 항상 0으로 두고, X축으로 갈수록 빨간색 증가, Y축으로 갈수록 초록색 증가하게 이미지를 구성해 볼 것이다.

그럼 아래와 같은 결과가 나올 것이다.

이중 반복문을 사용하여 각 픽셀마다의 색상을 구하는 방식으로 진행한다.

// Render
std::cout << "P3\n" << ImageWidth << ' ' << ImageHeight << "\n255\n";

for (int j = 0; j < ImageHeight; j++) 
{
    for (int i = 0; i < ImageWidth; i++) 
    {
    	// 색을 0 ~ 1범위로 정규화
        auto r = double(i) / (ImageWidth - 1); // zero base를 위한 min one
        auto g = double(j) / (ImageHeight - 1);
        auto b = 0.0;

 

먼저 각 색상 값 rgb를 0.0 ~ 1.0 범위의 값으로 정규화하여 계산한다.

그래픽스에서는 보통 이렇게 정규화된 실수로 색을 다루는데, 그 이유는 이렇게 단순한 색이면 불필요하지만,

색을 가지고 물리나, 조명 같은 여러 계산을 하게 될 때 유리하기 때문이다.

 

그래서 정규화된 값을 먼저 저장한 후에, 아래처럼 이미지 포맷이 사용하는 0 ~ 255 범위의 값으로 변경한다.

        int ir = int(255.999 * r);
        int ig = int(255.999 * g);
        int ib = int(255.999 * b);

        std::cout << ir << ' ' << ig << ' ' << ib << '\n';
    }
}

변경할 때 $255$가 아닌 $255.999$를 사용한 이유는, 부동소수점 오차 때문이다.

PPM 형식의 이미지파일에서 색상은 정수로 표현되는데, 0 ~ 1 정규화된 실수를 정수로 강제 변환하면 반올림이 아니라 버림으로 변환되기 때문이다.

 

그래서 이 정도면 최댓값인 255로 되어야 하는데 254가 되네? 같은 상황을 없애주기 위해 좀 더 과하게 255.999를 곱하여 255가 되는 범위를 늘려주는 것이다.

 

 

Build

코드 작성이 끝나면, 빌드를 진행하여 실행 파일을 만든다.

보통 프로젝트 파일 ⇒ x64 ⇒ Debug 에 생성되어 있다.

이제 해당 실행파일을 통해 이미지 파일을 생성할 건데, cmd를 이용한다.

$>$ 연산자를 사용하면 표준 출력을 파일로 연결해 주는 OS 기능을 사용할 수 있다.

 

먼저 해당 실행 파일이 있는 디렉터리로 이동하고, 해당 연산을 실행한다.

ppm파일이 생성된 걸 확인 후, ppm뷰어로 확인해 본다. 온라인 뷰어도 존재한다.

 

PPM 파일을 온라인으로 볼 수 있는 무료 온라인 도구 - ImageToStl

이 무료 도구를 사용하면 소프트웨어를 설치할 필요 없이 온라인으로 PPM (Portable pixmap format) 파일을 볼 수 있습니다.

imagetostl.com

 

Result

결과물을 보면, 처음에 예상했던 이미지가 출력되는 걸 확인할 수 있다.

 

 

Adding a Progress Indicator

이미지는 잘 생성되지만, 생성 시 무한 루프에 빠졌는지, 멈추었는지 확인이 불가능하기 때문에 진행 표시기를 추가한다.

main.cpp

for (int j = 0; j < ImageHeight; ++j) 
{
	// 진행 표시기
    std::clog << "\rScanlines remaining: " << (ImageHeight - j) << ' ' << std::flush;
    for (int i = 0; i < ImageWidth; i++) 
    {
        /* ... */
        std::cout << ir << ' ' << ig << ' ' << ib << '\n';
    }
}

// 완료시 출력
std::clog << "\rDone.                 \n";

지금은 굉장히 간단하기 때문에 불필요하지만, 후에 연산이 많아지면 볼 시간이 많아진다.

'C++ > Ray Tracing' 카테고리의 다른 글

[03] Adding a Sphere  (0) 2026.03.03
[02]Rays & Simple Camera & Background  (0) 2026.02.26
[01] Vector class  (0) 2026.02.24

+ Recent posts