[RT in One Weekend Series 7] Anti-aliasing (번역)

목차: Series 1: Index and Overview (Link)
이전 글: Series 6: Surface Normals and Multiple Objects (Link)
다음 글: Series 8: Diffuse Materials (Link)

이번 장에서는 Anti-Aliasing에 대한 설명을 작성한다. 예전에 Anti-Aliasing에 대한 글을 작성한 적이 있다 (출처 2). Anti-Aliasing을 간단히 설명하면 Object(물체)의 Edge 부분을 부드럽게 만드는 방법이다. 예를 들어 사진을 찍을 경우 물체의 Edge가 부드럽게 표현된다. Object Edge 부분을 부드럽게 만드는 방법은 Foreground와 Background 색상 값을 적당히 합쳐서 중간 색상 값을 선택하면 된다.

Anti-Aliasing 구현을 하기 전에 Camera Class를 먼저 생성한다. 이번 장에서 사용하는 Camera Class는 간단히 Ray만 생성하는 정도 수준이다. 하지만,  추후에 Camera Class를 수정하여서 Cool 한 장면을 연출할 계획이라고 한다. 아래 코드는 Camera Class 코드이다. 간단히 Ray를 생성하는 부분만 포함하고 있다.

#ifndef MKCAMERA_H
#define MKCAMERA_H

#include "mkray.h"

//MK: 간단한 Camera 클래스 
//MK: 이번 장에서는 단순히 Ray만 생성해주는 코드만 포함하고 있음
class camera{
    public:
        camera(){
            lowerLeftCorner = vec3(-2.0, -1.0, -1.0);
            horizontal = vec3(4.0, 0.0, 0.0);
            vertical = vec3(0.0, 2.0, 0.0);
            origin = vec3(0.0, 0.0, 0.0);
        }
        ray getRay(float u, float v){
            return ray(origin, lowerLeftCorner + u * horizontal + v * vertical - origin);
        }

    private:
        vec3 origin;
        vec3 lowerLeftCorner;
        vec3 horizontal;
        vec3 vertical;
};

#endif

Ray Tracer에서 drand48() 함수를 사용해서 Anti-Aliasing 로직을 구현할 예정이다. drand48() 함수는 대부분의 System에서 지원하는 함수이다. 출처 1에 따르면 아무 Random 함수를 사용해도 된다고 한다. Random 값이 0~1(보다 작은) 사이에 존재하면 된다.

그림 1: Anti-Aliasing 구현 방법 (출처 1)

그림 1은 기존에 색상을 계산하는 방법과 Anti-Aliasing을 사용하여 색상을 결정하는 방법에 대한 그림이다. 그림 1의 왼쪽 그림의 경우 기존 계산 방법이다. 각 Pixel 당 하나의 Ray만을 보내서 색상을 계산한다. 그림 오른쪽의 경우 하나의 Pixel 색상 연산을 위해서 여러 개의 Ray를 하나의 Pixel로 보내는 것을 확인할 수 있다. 아래 코드는 Anti-Aliasing을 구현한 Main 함수 코드이다. 앞 장에서 작성한 코드와 큰 차이는 없다. Ray를 생성하는 과정에서 Random 값을 사용하여 하나의 Pixel에 여러 개의 Ray를 보내는 부분만 수정하였다.

#include <iostream>
#include <fstream>
#include "mkray.h"
#include "mkhitablelist.h"
#include "float.h"
#include "mksphere.h"
#include "mkcamera.h"

using namespace std;

vec3 color(const ray &r, hitable *world){
    hitRecord rec;
    if(world->hit(r, 0.0, MAXFLOAT, rec)){
        return 0.5 * vec3(rec.normal.x() + 1, rec.normal.y() + 1, rec.normal.z() + 1);
    }
    else{
        vec3 unitDirection = unitVector(r.direction());
        float t = 0.5 * (unitDirection.y() + 1.0);
        return (1.0 - t)*vec3(1.0, 1.0, 1.0) + t * vec3(0.5, 0.7, 1.0);
    }
}

int main(){
    int nx = 400;
    int ny = 200;
    int ns = 100;
    string fileName = "Ch6.ppm";
    ofstream writeFile(fileName.data());
    if(writeFile.is_open()){
        writeFile.flush();
        writeFile << "P3\n" << nx << " " << ny << "\n255\n";
        hitable *list[2];
        list[0] = new sphere(vec3(0, 0, -1), 0.5);
        list[1] = new sphere(vec3(0, -100.5, -1), 100);
        hitable *world = new hitableList(list, 2);
        camera cam;
        for(int j = ny - 1; j >= 0; j--){
            for(int i = 0; i < nx; i++){
                //MK: Sampling을 통하여 Pixel 값을 결정 (Anti-Aliasing)
                vec3 col(0.0, 0.0, 0.0);
                for(int s = 0; s < ns; s++){
                    float u = float(i + drand48()) / float(nx);
                    float v = float(j + drand48()) / float(ny);
                    ray r = cam.getRay(u, v);
                    col += color(r, world);
                }
                col /= float(ns);
                int ir = int(255.99 * col[0]);
                int ig = int(255.99 * col[1]);
                int ib = int(255.99 * col[2]);
                writeFile << ir << " " << ig << " " << ib << "\n";
            }
        }
        writeFile.close();
    }
    return 0;
}

MK: 간단히 설명하면 기존에 Ray를 Pixel의 한 부분으로 보냈다면 Anti-Aliasing 구현을 위해서 그림 1과 같이 하나의 Pixel에 여러 개의 Ray를 보낸다. 여러 개의 Ray의 색상 값을 더한 다음 평균을 사용하여 최종 색상 값을 계산한다. 이렇게 여러 개의 Ray를 사용하여 하나의 Pixel 색상을 결정하면 Foreground와 Background 색상의 모두 합쳐서 중간값을 계산할 수 있다.

그림 2: Anti-Aliasing 코드를 사용한 결과 이미지

그림 3: Anti-Aliasing 코드를 사용하지 않은 결과 이미지 (출처3 – 이전글)

그림 2는 Anti-Aliasing 코드로 생성한 결과 이미지이다. 그림 3은 이전 글 (Anti-Aliasing을 제외)에서 생성한 결과 이미지이다. 그림2와 그림3에서 구의 테두리 색상이 달라진 것을 확인할 수 있다. 그림 2의 구가 그림 3의 구보다 더 선명한(?) 것을 확인할 수 있다.

MK: 구의 Edge 부분에 Random으로 생성된 Ray의 50% 정도는 아마도 구를 Hit 하게 되어서 구의 Normal Vector 값을 색상으로 변경한 값을 가진다. 하지만 나머지 50% 정도의 Ray는 Background의 색상 값을 가진다. 그 전체의 Ray 색상 값을 총 Ray의 개수로 나눈 값이 최종 색상이므로 구의 Edge가 부드러운 형태로 변한 것으로 판단된다. 실제로 구의 Edge을 제외하고는 크게 달라진 것이 없는 것을 확인할 수 있다.

출처

  1. http://www.realtimerendering.com/raytracing/Ray%20Tracing%20in%20a%20Weekend.pdf
  2. https://mkblog.co.kr/2018/12/14/gpu-anti-aliasing-aa-ssaa-vs-msaa/
  3. https://mkblog.co.kr/2019/06/02/rt-in-one-weekend-series-6-surface-normals-and-multiple-objects/

Leave a Comment