Post

3차원 Renderer 구현하기

정보과학 이미지처리 수행평가로 만든 프로그램인 Burenda의 제작 비하인드 내지 스토리입니다.
이딴 게 이미지처리가 맞냐고요? 결국엔 렌더링하면서 각 픽셀 색 계산해서 bmp에 넣었으니까 아무튼 이미지처리임 ㅇㅇ

1. 아이디어부터 렌더링 함수 작성까지

이미지처리 수행평가라는 것을 처음 듣고는, 한 10분 정도 뒤에 생각난 것이 블렌더였습니다. 이게 가능할까 하는 생각을 1분 정도 했는데, 안될 거 뭐 있냐는 생각으로 구현을 시작했습니다. 그렇게 저는 최악의 선택을 하게 됩니다. 큐브 추가와 카메라 위치 설정, 광원 설정을 구현합니다. 물론, 렌더링 함수 작성은 아직 뒷전이었기에 구현이래봤자 값을 받아와서 저장하는 정도의 구현입니다.
그러고 드디어 렌더링 함수 작성에 돌입하게 됩니다. 당시 다음과 같은 문제를 만나게 됩니다.

1. 각 픽셀이 내는 색은 어느 방향에서 가지고 오는가?

렌더링은 각 픽셀이 어느 색인지를 계산하는 과정입니다. 여기서는 카메라가 보는 방향으로 가장 가까이 있는 큐브 면의 색을 가지고 오면 됩니다. 다만 문제는, 카메라가 바라보는 정확히 그 방향에서만 색을 가져오면 그건 사진이 아니라 점을 렌더링하는 프로그램이 된다는 것이겠죠! 때문에 픽셀별로 바라볼 방향이 조금씩 달라야 하는데, 이걸 어떻게 계산해야 할까요?

  • 처음엔 그냥 무식하게 방위각과 고도를 픽셀의 위치에 비례해 조금씩 바꿨습니다. (방위각과 고도가 정식 명칭은 절대 아닐테지만, 지구과학 때 배운 죄표계랑 카메라 방향이 너무 딱 들어맞는 것 같았습니다. 그래서 그냥 저 명칭을 사용하도록 하겠습니다.) 카메라나 광원의 위치는 모두 극좌표계 형식이기 떄문에, 카메라가 원래 가리키고 있는 방향의 방위각과 고도에서 일정 범위를 가지고 오는 것입니다. 그런데, 이 방법에는 큰 오류가 있었습니다. 고도가 0도에서 멀어질수록 점점 왜곡이 심해져, 위에서 아래를 보는 식의 카메라 위치에서는 아예 렌더링이 되지 않는 것이었습니다! 생각해보면 당연한 일인데, 카메라 방향의 고도값이 -pi/2=-1.5708인 경우 방위각이 아무리 변해도 시선은 정확한 아래 방향으로 고정되겠죠. 즉 세계지도를 그리는 것마냥 고도가 0도에서 먼 부분은 더 많이 왜곡되어 있는 형태의 이미지가 생성되게 됩니다.
  • 그렇다면 방위각과 고도에서부터 직접 값을 빼지 않고, 적도좌표계와 지평좌표계의 관계처럼 현재 카메라의 방향을 원점으로 하는 구면좌표계를 잡으면 되겠네요. 그런 다음 그 원점에서부터 픽셀 위치에 맞게 일정 값을 방위각과 고도에 더하고 빼면 왜곡 없이 올바른 방향이 나올 것이고, 이 방향을 다시 방위각과 고도로 역계산하면 방향을 구할 수 있겠지요. 그러나 이 방법도 문제가 있었는데, 바로 수학적으로 어렵다는 것입니다(…). 계산을 오랫동안 시도했지만 된다 싶은 것 없었고, 그래서 이 방법도 포기합니다.
  • 그러고는 드디어 현재의 방식을 찾게 됩니다. 우선 현재 카메라 방향과 수직이고 카메라 방향으로 1만큼 떨어진 평면을 잡습니다. 이제, 그 평면이 투영면이나 카메라의 필름이라 생각하고 카메라에서 평면 위 ‘픽셀’에 해당하는으로 가는 반직선을 만듭니다. 카메라의 좌표는 알고 있고, 카메라의 방향으로부터 평면이 각 축과 이루는 각도의 (코)사인값들도 다 알 수 있습니다. 따라서 평면 위 ‘픽셀’의 좌표를 구할 수 있고, 역삼각함수를 통해 픽셀별 방향을 구할 수 있게 되는 것입니다. 이 방법은 180도 이상의 시야각을 가지는 카메라가 없다는 단점이 있지만 왜곡 따위 아예 없다는 장점도 가지고 있습니다. 그리고 무엇보다, 위 방법에 비해서 수학적으로 훨씬 더 쉽습니다! 그렇게, 드디어 렌더링 함수의 틀을 완성합니다.

    2. 각 면이 빛을 받아 내는 색은 언제 어떻게 계산하는가?

    광원 설정이 구현되어 있다는 것은, 광원 위치에 따라 큐브의 면별 밝기가 다 다르다는 뜻이겠죠? 다시 말해 렌더링을 하려면 모든 면의 밝기가 다 계산되어 있어야 한다는 것입니다.
    우선, 그림자 구현 따위는 불가능합니다. 이가 가능하려면 각 면을 하나의 스크린으로 보고 빛이 오는 방향을 역으로 따라 올라가서 앞에 큐브가 있는지 확인하거나, 말 그대로 레이트레이싱을 구현해야 합니다. 다른 방법도 있겠지만 구현할 수 있는 방법은 일단 없다고 생각했고, 그래서 그림자 구현은 하지 않기로 합니다.
    그렇게 되면 작업이 많이 쉬워집니다. 각 면에 대해 빛이 오는 방향을 확인해, 만약 뒤쪽에서 빛이 온다 싶으면 그 면은 엄청 어둡게 칠하고 그게 아니면 빛의 입사각을 구해 코사인값에 비례해 밝기를 조절해주면 됩니다. 그렇다면 이제 이 밝기를 언제 계산하는지에 대한 문제가 남게 됩니다.

  • 만약 큐브가 추가될 때마다 계산한다고 하면, 큐브의 초기 색상 정보를 저장할 필요 없이 계산된 색상만 저장해도 됩니다. 그런데 빛을 바꿀 때마다 필요한 연산량이 늘어난다는 단점이 있습니다.
  • 만약 매 렌더링 직전에 계산한다고 하면, 반대로 큐브의 원래 색상만 저장해도 됩니다. 다만 빛이 안 바뀌어도 매 렌더마다 새로 계산해야 하고, 떄문에 렌더링 시간이 더 오래 걸립니다.

이런 방법 중 저는 후자를 골랐습니다. 이유는 어차피 렌더링 시간에 비하면 면별 색상 계산 시간이 훨씬 더 짧기 때문에 실제 렌더링 시간에 끼치는 영향은 적었기 때문입니다. 여기에 더해, 추후에 만든 저장/불러오기 기능이 전자에 쓰기 좀 애매했습니다.

3. 렌더링 구현

모든 것이 준비되었으니 렌더링을 구현합니다. 위 1.에서 설명한 대로 평면 하나를 잡아 픽셀들의 방향을 잡은 뒤, 그 방향으로 반직선을 연장할 때 가장 가까이 만나게 되는 면을 찾습니다. 면의 색을 이미 계산되어 있으므로, 그 픽셀에는 그 색을 넣어주면 됩니다. 이렇게 해서 처음으로 사진을 출력하기까지 약 4시간 정도를 쓰게 됩니다.

2. 무수한 잡기능의 추가

렌더링을 할 수 있게 되었으므로, 이제 생각나는 대로 기능을 추가해 살을 붙이면 되겠죠?

  • 우선 위에 나왔던 저장 및 불러오기를 만들게 됩니다. 저장이라기에는 그냥 .txt 파일에 카메라, 광원, 큐브 정보를 모두 출력해놓는 것입니다. 그래도 입출력 예제를 파일 하나로 둘 수 있으니 얼마나 간편한가요?
  • 큐브를 삭제하는 기능도 만듭니다. 그냥 vector에서 pop_back()하는 것 한 줄을 추가해 둔 것입니다.
  • 그러고는 안티앨리어싱을 추가합니다. 안티앨리어싱이란 흔히 계단 현상이라 하는 것을 없애는 데 쓰는 기술입니다…라기엔 그냥 해상도를 올려서 렌더링하고 여러 픽셀의 색을 평균내는 것입니다.
  • 다음은 배경색입니다. 각 광선에 대해 만나는 큐브가 하나도 없을 경우에는 그 배경색으로 색을 설정해주면 됩니다.
  • 시야각과 출력 크기 설정도 만들었습니다. 각 광선이 발사되는 방향을 가로세로 픽셀수와 시야각의 크기라는 변수를 이용해 잘 나타내주면 됩니다. 그렇게 해서 결과물이 나오게 됩니다…

    3. 결과물

    은 대충 이렇습니다. /rendered-images에서 좀 더 많은 결과물 예시를 찾아볼 수 있습니다.
    나름 사진이 예쁘게 나왔습니다. 물론 큐브가 조금만 늘어나도 사진의 렌더링 시간이 좀 선을 넘게 증가하기 때문에, 이게 과연 맞나 싶은 생각이 들긴 했습니다만 아무튼 결과물이 예쁘면 장땡 아니겠습니까?
    뭐 이런 식으로 만들어서 발표를 했고, 다행히도 만점을 받았습니다. 아마 22년도 안에 하게 될 PS 이외의 정보 프로젝트 중에는 이게 제일 잘 된 것이 되지 않을까 싶습니다.

This post is licensed under CC BY 4.0 by the author.