마우스를 이용한 View 회전

예전에 나이스가이님이 질문한 마우스를 이용해 현재 화면을 회전하면서 화면상의 물체를 살펴보는 방법에 대해 간단하게 설명해 보려한다. 전문용어(?)로는 Arcball 기법이라고 하는데, 이 Arcball을 구현하는 방법으로 직접 회전행렬을 계산해서 OpenGL의 glMultMatrix를 이용해 적용하는 방법 하나와 x와 y축에 대한 회전각도만을 계산해서 glRotatef를 이용해 적용하는 방법이 있다. 첫번째 방법은 NeHe의 강좌에 소개되어져있으나 URL은 http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=48 이다. 하지만 필자는 두번째 방법을 이용해 Arcball를 구현해 보려고 한다. 이유인 즉, 첫번째 방법은 OpenGL에서는 기본적으로 행렬 Type와 행렬간의 연산자 함수를 제공해주지 않으므로, 회전행렬을 직접 구하기 위한 행렬 Type을 개발자가 직접 정의해줘야 하고 행렬연산을 만들어줘야하는 번거로움이 있는 반면, 두번째 방법은 단지 x와 y축에 대한 회전각도 값을 실수형으로도 충분하기 때문이다.

글자만 있으면 썰렁하므로 별 의미도 없을법한 최종 결과 스크린샷은 아래와 같다.


화면상에 OpenGL의 GLUquadricObj을 이용하여 실린더를 6개를 그렸으며, 마우스를 이용하여 실린더 6개를 회전시키면서 살펴볼 수 있다. 보다 자세한 조작법은 마우스 오른쪽 버튼을 누른 상태에서 좌우로 마우스를 이동하면 Y축으로 회전되며 상하로 마우스를 이동하면 X축으로 회전한다. 이러한 사용자 액션과 반응을 염두해 두고 코드를 살펴보면 이해하는데 큰 도움이 될 것이다.

먼저 마우스 조작에 따른 Arcball 기법을 적용하기 이전에 화면상에 6개의 실린더를 그려주는 코드는 다음과 같다.

int DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	glTranslatef(0.0f, 0.0f, -20.0f);

	bool bToggle = true;
	for(float offset=-5; offset<=5; offset+=2) {
		if(bToggle) glColor3f(0.6f, 0.6f, 1.0f);
		else glColor3f(1.0f, 0.6f, 0.6f);
		bToggle = !bToggle;

		glPushMatrix();
		glTranslatef(offset, 0.0f, 0.0f);
		glTranslatef(0.0f, 0.0f, -8.0f);
		gluCylinder(obj, 1.0f, 1.0f, 16.0f, 38, 4);
		glPopMatrix();
	}

	return TRUE;			
}

코드를 보고 이미 짐작하고 계실 분도 있겠지만, 이 코드는 NeHe의 강좌에서 제공하는 코드를 기본으로 하고 있다. 혹시 필요한 분들을 위해 전체코드를 다운로드 할 수 있도록 하겠다.

이제 위의 코드에서 마우스를 이용해 상하좌우로 회전시키면서 6개의 실린더를 구석구석 관찰하는 코드를 추가해보자.

먼저 마우스의 Drag를 통해 계산될 X축과 Y축에 대한 회전값을 위한 변수는 다음과 같다.

GLfloat xAngle;
GLfloat yAngle;
POINT mouseDownPt;

언급하지 않은 mouseDownPt 변수가 있는데, 이것은 마우스 버튼을 누른 좌표 지점을 저정하기 위한 변수이다. 마우스 버튼을 누른 지점과 마우스 버튼을 누른 상태에서 마우스를 이동하였을때 X축과 Y축으로 얼마만큼 이동되었는지를 계산하고 이 계산된 값을 이용해 xAngle와 yAngle값이 계산된다.

일단 xAngle와 yAngle가 계산되었다고 가정하고 최종적으로 회전을 적용시킨 코드는 가장 앞서 언급한 코드인 DrawGLScene 함수의 수정에 있다.

int DrawGLScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();							
	glTranslatef(0.0f, 0.0f, -20.0f);

	glRotatef(xAngle, 1.0f,0.0f,0.0f);
	glRotatef(yAngle, 0.0f,1.0f,0.0f);

	bool bToggle = true;
	for(float offset=-5; offset<=5; offset+=2) {
		.
		.
		.
	}

	return TRUE;			
}

새롭게 추가된 오렌지색 두줄의 코드가 전부이다. X축과 Y축의 회전에 맞게 glRotatef의 함수의 인자가 사용되었음을 주의해서 보기 바란다.

이제 마지막으로 마우스의 액션에 따른 실제 xAngle와 yAngle의 값을 계산하는 방법을 알아보도록하자. 이 계산 코드는 마우스 액션에 대한 Event에 위치하게 되는데, MouseDown, MouseMove, MouseUp 이벤트에 그 코드가 존재한다.

	case WM_LBUTTONDOWN:
	{
		mouseDownPt.x = GET_X_LPARAM(lParam);
		mouseDownPt.y = GET_Y_LPARAM(lParam);

		SetCapture(hWnd);

		return 0;
	}

위의 코드는 간단히 앞에서 정의한 mouseDownPt 변수에 가장 처음 마우스 버튼이 눌려진 위치를 저장하고 있으며 SetCapture  API를 호출해서 마우스가 Window를 벗어나도 지속적으로 Mouse Message를 받을수 있도록 하고 있다.

	case WM_MOUSEMOVE:
	{
		if(GetCapture() == hWnd) {
			int X = GET_X_LPARAM(lParam);
			int Y = GET_Y_LPARAM(lParam);

			xAngle += (Y-mouseDownPt.y) / 3.6;
			yAngle += (X-mouseDownPt.x) / 3.6;

	                InvalidateRect(hWnd, NULL, FALSE);

			mouseDownPt.x = X;
			mouseDownPt.y = Y;
		}

		return 0;
	}

위의 코드는 마우스를 이동했을때 호출되는 코드인데, GetCapture를 이용해서 현재 마우스 버튼이 눌러졌는지를 검사하고, 만약 마우스가 눌려진 상태에서 움직였다면 바로 여기서 xAngle와 yAngle를 계산한다. 계산공식을 눈여겨 보길 바란다. 3.6으로 나눠주고 있는데, 이것은 마우스를 1 픽셀 이동했을 경우 1/3.6 회전, 즉 0.27777778도 회전하게 되는데, 이 회전량이 사용자가 가장 자연스럽게 느껴지므로 3.6으로 나눴다. 좀더 회전정도를 강하게 하고 싶다면 이 값을 줄이면 회전이 팍팍!! 되도록 할 수 있다.

	case WM_LBUTTONUP:
	{
		ReleaseCapture();
		return 0;
	}

끝으로 위의 코드는 마우스 버튼을 눌러 이동하면서 물체를 이리저러 살펴보다가 마우스 버튼을 누름을 해제시킬때 발생하는 코드로써, ReleaseCapture API를 호출한다. ReleaseCapture는 SetCapture를 호출한 윈도우가 반드시 호출해줘야 하는 Couple API로써 SetCapture에 의한 마우스 메세지의 독점처리를 해제하는 함수이다.

이상으로 간단하게 마우스를 이용한 Arcball 기법에 대해 살펴보는 것을 마치도록 하며, 아래에 해당 소스를 링크한다.

“마우스를 이용한 View 회전”에 대한 8개의 댓글

    1. 위의 글은 원리라고 하기에 매우 미천한 글이랍니다. 좀더 정확히하려면 트랙볼 기법을 사용해야 하지만.. 별반 차이가 없어.. 비교적 구현이 쉬운 방법을 설명드린 내용이랍니다.

  1. 좋은글 감사합니다. ^^
    그런데 “NeHe의 강좌”와 이 강좌랑은 회전하는게 좀 다른 것 같습니다.
    NeHe는 세로로 180도 돌린 후 오른쪽으로 돌리면 오른쪽으로 도는데
    여기 예제는 세로로 180도 돌린 후 오른쪽으로 돌리면 왼쪽으로 도네요.
    NeHe강좌 처럼 구현을 해야 되는데 만만치 않군요 ㅠㅠ

    1. 카메라의 위치가 다르기 때문이 아닌가 싶습니다. NeHe의 카메라 위치와 위의 강좌의 카메라 위치가 180도 다른 위치가 아닌가.. 싶네요..

    1. 쿼터니안은 일반 4×4 행렬에 비해 속도.. 데이터 크기량.. 말씀하신 짐벌락과 같은 문제에서 모두 뛰어난 회전 도구입니다. 수학이론적으로 접근하면 이해하기 어려운 부분이 있을텐데… 먼저 쿼터니안을 사용하는 방법으로 해서 일단 사용하신후.. 찬찬히 수학 이론을 다시 학습해보시면 좋을듯하네요.. 뭐.. 저는 이랬다는 것입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다