8분의 1원을 그려보세요.

미국에서 꽤나 유명한 프로그래밍 인터뷰 관련 책이 있다. 심심해사 잠시 들쳐보다 맘에 들지 않는 문제 풀이가 있어 이 글에서 이야기 좀 하고자 한다. 그래픽스에 관련된 건데 간단한 문제다.

반지름 값이 주어졌을 때 (0, 0)을 중심으로 하는 8분의 1원을 그리는 함수를 만들어보라. 8분의 1원이란 12시 방향에서 1시 30분 방향까지만 그리는 것을 말한다. 픽셀은 setPixel(x, y) 함수로 그리는 것으로 가정한다.

그냥 위 그림처럼 동그라미 일부, 즉 호를 그리면 된다. 그런데 문제 풀이를 보니 좀 고되게 풀고 있었다. 바로 원을 음함수로 표현하고 있었다 (수식은 여기서 그렸음).

모 이건 고등학교 1학년 때 배우는 산수다. 여기서 우리는 1사 분면에서 그림을 그리기 때문에 음수인 경우를 무시하면 되겠다. 그래서 이 수식을 가지고 픽셀을 찍고 있다. 이것만 해도 좀 삽질스러운데 더 못 마땅한 것은 8분의 1만큼의 호를 그리기 위한 구간 계산이었다. 어떻게 보면 센스있는 발견이라고 할 수도 있겠지만, (0, r) 부터 점을 찍기 시작해서 “y가 x보다 작아질 때까지” 그리도록 하였다. 1/8 원은 기울기 45도인 직선 (y=x) 위에서 끝나기 때문이다. 그래서 최종적으로 책에서 제시하고 있는 답변은:

void drawEighthOfCircle(int radius){ 
int x = 0, y = radius;
while( y <= x ){
y = Math.sqrt((radius * radius) - (x * x)) + 0.5;
setPixel( x, y );
x++;
}

대충 이러하다. 여기서 또 반올림까지 고려해서 0.5까지 더 하고 있다. 썩 맘에 들지 않는다. 특히 구간 설정이 너무 맘에 안든다. 만약 1/17 원을 그리라면? 그 땐 “y <= x” 이런 것으로는 안되고 직접 x, y 좌표를 구해야만 하는 번거로움이 추가된다. 더 나가 1, 4사 분면에 원이 걸친다면 음수인 경우를 따로 처리해야 할 것이다.

그런데 극형식을 빌어 표현하면 코드는 매우 간단해진다.

얼마나 깔끔한가? 이 문제가 요구하는 답은 단순히 theta를 45도에서 90도까지 바꿔주며 점 찍으면 끝난다. 더 나가 극형식을 쓰면 속도 및 가속도 구하는 것도 매우 간단해진다.

void drawEighthOfCircle(int radius){
static const double PI = 3.141592654;
static const double RESOLUTION = PI/180;
for (double theta = PI/4; theta <= PI/2; theta += RESOLUTION)
setPixel(r*cos(theta), r*sin(theta));
}

그러나 생각해보면 극형식을 이용한 코드는 몇 가지 문제점을 가지고 있다.

그리는 범위가 상당히 직관적으로 표현이 되지만 화면 해상도를 고려해 적절한 스텝을 결정해야 한다는 문제가 있다. 내가 쓴 코드는 단순히 1도(=pi/180) 만큼 스텝을 줘서 그렸기 때문에 루프는 45번 정도 순회할 것이다. 그런데 확대된 화면을 그린다고 하자. 그러면 이 코드는 듬성듬성 점만 찍고 말 것이다. 그래서 이 점들 사이를 선분으로 이어주는 코드를 넣거나 아니면 더 잘게 점을 찍어야 할 것이다. 반면 내가 삽질스럽다고 욕한 코드는 이 점에 있어서는 좋다. 정확하게 x 좌표를 하나씩 증가 시키기 때문에 해상도 문제를 고려할 필요가 없어 보인다.

계산량을 생각하면 cos, sin 같은 초월함수는 x87 명령어에 있기도 하지만 이런 경우는 미리 테이블을 만들어 놓을 수 있다. 또, cos, sin 입력 단위를 도로 한다면 루프 순회도 double이 아닌 정수로도 표현 가능하다. 실제 그래픽카드 드라이버에서 어떤 방식으로 원의 호를 그리는지는 잘 모르겠다. 거기서는 최대한 정수 연산으로 바꿔야 하기 때문에 sqrt 만 회피한다면 책에서 제시한 방식이 더 나을지도 모르겠다.

여러분은 어느 코드가 더 나아 보입니까? 저는 컴퓨터 그래픽스 전문가는 아닙니다.

(추가) 1/8 원을 그리는 것은 원을 그리는 기본 연산 중 하나입니다. 일반적으로 이런 문제는 2차원 픽셀 화면에 선분을 그리는 것과 같은 스캔 컨버전 문제인데 픽셀 단위로 그려야 한다면 원래 책에 있는 방식이 합리적입니다. 제가 생각한 극형식은 표현이 매우 간단하고 복잡한 곡선(음함수로 표현이 불가능한)을 그리기에 매우 편리합니다만 지적했듯이 픽셀 단위가 아닙니다. 목적에 따라 적절하게 선택하시면 되겠습니다.

참고: http://mrl.snu.ac.kr/courses/CourseGraphics/OutputPrimitives.ppt

by object | 2009/02/04 15:03 | 컴퓨터 | 트랙백 | 덧글(17)
트랙백 주소 : http://minjang.egloos.com/tb/2223951
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by 櫻くん at 2009/02/04 15:21
실제로 원을 그릴 때는 1/8짜리 테이블을 만들어놓고 그걸 이리저리 flip해서 쓰는 걸로 알고 있습니다.
근데 그렇다는 사실을 안 게 아주아주아주 먼 옛날 일이라서 지금도 그렇게 하는지는... =_=;;;
Commented at 2009/02/04 15:45
비공개 덧글입니다.
Commented by realisty at 2009/02/04 16:02
그래픽스에서 고전적인 scan conversion 문제입니다.
http://mrl.snu.ac.kr/courses/CourseGraphics/OutputPrimitives.ppt
를 참조하시면 되겠네요.
링크는 서울대학교 컴퓨터공학부 이제희 교수님의 강의자료입니다.
Commented by object at 2009/02/04 16:15
좋은 자료이네요. 스캔 컨버전을 저렇게 자세한 슬라이드로 구경해본건 첨이네요.
Commented by Ray at 2009/02/04 17:09
그래픽스 라이브러리는 항상 속도와의 싸움이 중요하였으므로 속도만 본다면 원래 답안이 괜찮네요. 그런데, 문제에서 뭐를 중요시해야 하는지 안나왔으므로 실제 코딩테스트라면 뭘 중요시 할지 잘 생각해야 겠네요.
Commented by object at 2009/02/04 18:07
원을 그리는 기본 연산에 목적을 둔다면 원래 답안이 더 나은 것 같습니다.
Commented by 민군 at 2009/02/04 17:43
http://helloktk.tistory.com/entry/Circle-Drawing-Algorithm

이정도가 해답이 되지 않을까요? 알고리즘 자체도 1/8원을 만들어내니 딱맞는 것 같습니다.
Commented by object at 2009/02/04 18:06
감사합니다. 스캔 컨버전 관점에서는 책에 있는 방식이 더 합리적이네요. 저는 극형식으로 이런 저런 예쁜 그림 그리는 코드에 익숙해서 그런지 다르게 생각했었습니다.
Commented by 겐도 at 2009/02/04 18:21
적어도 삼각함수의 경우, r의 크기에 따른 theta의 delta가 달라지기 때문에 미리 계산해서 재사용은 힘들겠죠. 정수계산을 이용한 원그리기도 일단은 첫번째 코드의 아이디어를 기반으로 합니다. 1/8 구역에서는 x값에 대한 y값이 하나 존재하기 때문에 x를 반복 변수로 잡고 돌려버릴 수 있기 때문이죠. 라인이든 원이든 직접 계산값을 토대로 이번 y가 동일할지 1 감소할지 결정하면 됩니다.
제가 보는 관점에서, 이 문제의 함정은 정밀도인것 같습니다. delta를 충분히 정밀하게 잡기가 그리 쉽진 않죠. 1/10도씩 움직인다 해도 30인치급 모니터에선 좀 날카롭게 보일껍니다. 원래 원그리기 문제를 내고 싶었지만 저정도의 생각을 바로 내기는 힘들어서 1/8이란 힌트를 좀 준것 같군요.
Commented by lolized at 2009/02/05 00:57
어디에 쓰일지는 모르겠지만 이렇게 기본적인 레벨의 그리기 함수라면 보통은 코드보단 속도가 관건이겠죠. 사용자가 내부를 볼 일은 별로 없을테니까요.

우선 극좌표와 x좌표의 근본적인 차이를 생각해보면 루프 도는 횟수는 서로 다를 수 밖에 없겠지만, (아마도 극좌표계가 더 많이 그릴겁니다. 가장 듬성듬성한 영역에서도 x가 1씩 뛰어야 할테니까요) 똑같은 횟수를 돈다고 가정할 때 cos, sin 함수를 한번씩 부르는 비용과 sqrt 함수를 한번 부르는 비용의 문제일텐데... 일단 표준 C함수를 이용하면 전자가 더 느린 것 같네요.
Commented at 2009/02/05 11:17
비공개 덧글입니다.
Commented by eoh at 2009/02/05 11:38
글꼴의 외곽선을 그리는 문제가 생각나네요.
곡선을 그리는데에 있어서 큰 문제는 2가지가 있다고 생각합니다.
1) 선의 스캔컨버전, 2) 곡선의 평가
후자의 이야기는 결국 곡선 자체를 국소적으로는 직선으로 평가하게 된다는 이야기입니다.
따라서 파라미터와 그 평가 간격의 문제가 됩니다..

1) 안티 에이리어싱을 하지 않는다면,
일반적으로는 링크해주신 프레젠테이션에서의 알고리즘을 쓰는게 적합하다고 생각합니다.

안티 에이리어싱을 고려한다면,
스캔 컨버전에 있어서 선을 그리는 문제는 사실상 선의 굵기가 0 이 아닌 상태로
그려야 한다는데에서 문제가 발생합니다. 즉 선을 굵기를가진 곡면으로 변환해서 생각해야하죠.
저 같은 경우에는 그럴때에 픽셀단위의 단위 정사각형에 두께를 가진 선 -
즉 곡면이 차지하는 면적의 비율만큼을 지정해주는 형태로 구현한적이 있는데 꽤나 잘 작동하더군요.
(물론 속도는 무지하게 느려집니다만.)

2) 한편, 곡선 자체를 얼마만큼의 시간 간격의 직선으로 평가하면 좋은가에 대한 문제에 대해서는..
graphics gems 시리즈에서와 같이 많은 연구 결과가 있긴 합니다만, 그중에 인상적인것으로서는
곡선을 시간 변수의 식으로 치환한뒤, (물론 극좌표에서도요.)
구심가속도에 의해 구간을 결정하는 방법도 괜찮아 보이더군요.
예를 들면, 구심가속도에 의해 1-2픽셀 중심방향으로 이동되는 시간간격으로 직선을 그린다거나..
하는식으로요.

물론.. 제가 설명한것은 꽤나 일반적인 방법이기 때문에, 원에 대해 최적화를 한 코드에 비해서는 느릴겁니다.
Commented by object at 2009/02/06 04:29
자세한 답변 감사합니다. 저는 단순히 동그라미를 그리는 것에만 신경을 쓰다보니 극좌표가 훨씬 쉽게 느껴졌는데 스캔 컨버전 이야기가 나오니 복잡한 이야기가 많이 숨어있었군요.
Commented by jhsieben at 2009/02/09 11:17
안녕하세요, RSS 구독중인 대학생입니다! 흥미로운 글들이 많이 올라와서 잘 보고 있습니다.

'동그라미를 그린다'는, 어찌보면 기본적인 일에도 이렇게 고려할 사항이 많군요...
수학에 약해서 그런지 전부 이해하진 못했지만,
포스트만 봤을때 두번째꺼가 더 마음에 들어요!
Commented by C at 2009/02/12 20:09
ㅋㅋㅋ

CG에서는 기초적인 이야기인데, cos, sin 하는 거 보고 풉... (죄송)

게다가 삼각함수 계산량을 고려해 테이블... ^^;

그런 그림은 그래픽카드 드라이버에서 그리는게 아니고, 그래픽 라이브러리(커널)에서 그리죠. 그런 프리미티브 형태 보다 복잡한 그래프나 그래프의 조합은 CAD Kernel이나 Math Kernel과 함께 사용하죠.
Commented by sunsky at 2009/03/06 10:38
흥미로운 생각을 글로 올려주셔서 잘 보았습니다. 앞으로도 RSS로 계속 보게 될 것 같아요.
java 공부를 하고 있지만 아주 재미있습니다.
글을 열심히 써주신 노고에 감사드리네요. ^^
Commented by 정수연산 at 2011/02/23 01:10
사실 sqrt 하나만 있는 저 식은 모두 곱셈 나눗셈마저 없이 정수 연산만 가지고도 할 수 있도록 간단히 바꿀 수 있고, 어느 해상도에서도 쉽게 끊어지지 않게 그릴 수 있다는 점에서 첫 번째 알고리즘에 장점이 있겠네요. 두 번째 방법은 확실히 어떤 의미인지 이해하기 쉽네요~

:         :

:

비공개 덧글

<< 이전 페이지 다음 페이지 >>





by 김민장 2008 이글루스 TOP 100
최근 등록된 덧글
개발자 입장에서의 수많은 ..
by Jiyoon at 02/04
저도 아들 돌잔치때 돌잡이 ..
by 박상욱 at 01/18
미국 대학원 원서 작성중에 p..
by 태클사이야 at 01/13
TO: 박PD 로그인 하지 않아..
by 박응용 at 01/10
http://gigglehd.com/zbx..
by dhunter at 12/28
우와.. 좋네요. 태반이 ..
by 윤광배 at 12/17
항상 좋은 글 잘 보고 있습니..
by y2k at 11/23
글이 좋아서 제 블로그에 담..
by 쏭섭 at 11/23
최근 등록된 트랙백
조엘 스폴스키의 강연 (Sta..
by 인덕원칸타타
[Redis] sds.c를 분..
by 조급하지말고 천천히
메뉴릿
이글루 파인더

website counter

Add to Google

rss

skin by 이글루스