ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 신기한 벡터의 내적과 외적
    프로그래밍/3D 2005. 7. 22. 21:21

    이글은 하이텔 게임개발자 동호회(gma)의 이태경(수퍼유저)님의 글입니다.
    ---------------------------------------------------------------------------------------------------------------
    벡터의 내적과 외적, 법선벡터를 알자.

    먼저 벡터의 내적과 외적을 알기전에 벡터에 대해 조금만 얘기하겠습니다.
    당연히 수학적인 부분이지만 초등학생도 알수 있도록 쉽게....

    1. 벡터

      2차원 좌표상에 점을 표시할때 일반적으로 x,y 두개의 좌표를 가지고
      화면의 점을 그린다. 이때 수학적으로 점이란 눈에 안보이는 것이지만
      점을 구성하는 좌표 성분으로 P(x,y)라고 지정한다.

      벡터란 원점을 기준으로한 점이라고 생각하면 쉽게 설명할 수 있을 것
      이다. V(x,y)를 표시할때 결국 (0,0)에서 (x,y)의 방향을 가르키는
      말이며 v(2,2)와 v(3,3)은 결국 크기만 다르지 같은 방향을 가르키고
      있다.


    2. 단위 벡터의 특성

      단위 벡터란 크기가 1인 벡터를 얘기한다.

      0 에서 1까지의 실수는 아무리 곱해서 절대로 1을 넘지 않는다.
      이 특성이 단위 벡터에서도 나타난다.
      단위 벡터끼리 곱하는 연산은 1000만번을 한다하더라도 단위 벡터다.
      사실 위의 예기는 아주 중요한 얘기이며 이 간단한 사실만으로
      연산을 아주 간소화 할수 있다.
      
      그럼, 단위 벡터는 어떻게 만드는가?
      v(1,1)와 v(2,2)는 크기가 다르지만 방향을 같다고 했다.
      두 벡터를 크기는 무시하고 오직 방향만 계산하고 싶다고 할때
      단위 벡터를 만든다. 결국 크기는 1이니까..

      같은수에다 같은수를 나누어 보라..
      예를 들어 8 / 8 = 1, 7676 / 7676 = 1
      역시 1이다.

      단위 벡터도 이렇게 구한다.

      벡터 v(x,y)가 있을때 벡터의 크기는 sqrt(x*x + y*y)이다.
      그럼, 이걸로 나누면 땡이다.

      v = sqrt(x*x + y*y);
      vx /= v ;
      vy /= v ;

      이제 벡터의 크기를 구해보자..

      크기 = sqrt(x*x + y*y)는 1이 나와야 한다.
      
      이것은 영어로 Normalize라고 한다.

    3. 벡터의 내적

      이제 좀 어려운 부분을 얘기 하겠습니다.
      벡터의 내적은 꼴도보기 싫은 수학정석에 나와 있습니다.
      영어로 DotProduct 혹은 inner-Product, Scalar-Product라고 하더군요..
    벡터의 내적 공식은 두 벡터가 있을때 두 벡터 사이의 각도를
      구하는 공식이죠..

      그럼..  꼴도보기 싫은 수학정석에

      cos(theta) = a*b / |a|*|b| 라고 되어 있습니다.

      (참고) *는 곱셈이 아니고 . 이지만... 표기할게 없어서..

      공식이 어떻게 나왔나고 묻지는 마세요.. 책에 그렇게 되어 있기에..

      위 공식에서 만일 a와 b가 단위 벡터라면 |a|*|b|는 1이겠지요..

      그럼.. cos(theta)= a*b로 간략화 營윱求  

      a*b 벡터의 연산은 성분끼리 곱하면 됩니다.

      cos(theta) = a_x*b_x + a_y*b_y ;

      (예)

      float a_x, a_y, b_x, b_y;
      float v, costheta, theta;

      a_x = 1.0;    a_y = 3.0;
      b_x = 3.0;    b_y = 1.0;

      // 먼저 단위 벡터로 만든다.
      v = sqrt(a_x * a_x + a_y * a_y);
      a_x /= v;
      a_y /= v;
      v = sqrt(b_x * b_x + b_y * b_y);
      b_x /= v;
      b_y /= v;
      
      theta = a_x*b_x + a_y*b_y ;
      costheta = cos(theta);

      음.. 이제 costheta를 구했다면 황당한 값이 나옵니다.

      이걸가지고 어떻게 하란 말야..

      cos(theta) = rad 라고 했을때

      theta = acos(rad) 이렇게 역으로 구할수 있습니다.

      그럼 위의 프로그램에 더 첨가합니다.

      theta = acos(costheta);

      결국 우리가 바라는 벡터 사이의 각이 나왔습니다.

      신기하져?

      (참고) 라디안 값
      일반적으로 sin, cos, tan 함수에서 sin(theta) = rad 일
      theta = asin(rad) 이렇게 역함수가 존재 합니다.
      tan는 atan 흔히들 아크 함수라고 하져..

      그리고 각도를 얘기할때 0~360도 얘기하는 것은 Degrees 값이라고
      하며 수학에서는 보통 Radian 값을 씁니다.

      0에서 180까지의 Degree 값을 얘기할때 라디안 값은
      0에서 3.141592(즉 PI) 값까지 나오지요..

      180 : 3.141592 = degree : rad  이렇게 되지요..

      rad = degree * 3.141592654f / 180 ;
      degree = rad * 180 / 3.141592654f ;


      그럼. 다시 벡터를 얘기합니다.

      theta = acos(costheta); 에서 나온 값은 라디안 값이므로
      degree = theta* 180 / 3.141592654f ;

      해보면 일반적인 각도가 나옵니다.

    4. 벡터의 외적

       벡터의 외적이 꼴도 보기 싫은 고등학교 수학 정석에 있었는지는
       잘 모르겠네요.. 뒷장까지 본적이 없어서..

       벡터의 외적은 그럼 무엇일까요? 벡터 사이의 각이 아닌
       반대 방향 각을 구하는 공식일까요? 아닙니다.
       벡터의 내적과는 성격이 좀 다릅니다.
       벡터의 내적은 결국 라디안 실수 값이 나오지만
       외적을 구하는 공식은 그냥 벡터가 하나 더 생깁니다.

       두개의 벡터가 있을 기준점에 수직으로 못을 하나 꽂으면
       못 방향으로 벡터가 하나 생깁니다. 두 벡터에 수직인 벡터
       가 하나 더 생기는 셈이지요..
       영어로 CrossProduct라고 하져..

       그림으로 설명하면 더 쉬운데... 쩝..

       v1(x,y,z)와 v2(x,y,z)가 있을 (0,0,0)을 출발점으로 한 위로
       우뚝선 벡터 n(x,y,z)가 하나 더 생긴단 말이져..

       두벡터에 수직인 벡터는 사실 두게 있습니다.
       위아래...

       보통 시계 방향이냐 반시계 방향이나 따라서 한가지만 뽑아냅니다.

       다음은 내적을 구하는 연산입니다.
       외우지는 마세요.. 그냥 베껴 쓰면 되니깐...^^;

       n_x = v1_y * v2_z - v1_z * v2_y;
       n_y = v1_z * v2_x - v1_x * v2_z;
       n_z = v1_x * v2_y - v1_y * v2_x;

       주의 하실점은 반드시 벡터를 단위벡터로 만들고 하세용..


    5. 법선 벡터

       법선 벡터는 3D 그래픽 프로그래밍에서 흔히 노말 벡터라고 합니다.
       3차원 상에 점(vertex)가 3개가 있다고 합시다.
       그럼.. 3개의 버텍스 사이에 면이 생깁니다. 일종의 평면이지요..
       이 노말 벡터는 의 앞뒤를 가르키는 벡터입니다.

       면을 앞 뒤를 구분하는 이유는 바로 연산량과 관계 있습니다. 구와 같은
       물체를 안쪽면까지 그린다면 엄청 느려지겠지요.. 그래서 뒷쪽 면은
       연산에서 제외 시켜 버립니다. 이것은 Cull_face 혹은 Cull_mode라고 하져..

       또한 노말 벡터는 라이트와 밀접한 관련이 있습니다. 무슨 말인가 하면
       당구를 생각합시다. 공을 한쪽 벽에 튀길때 들어 오는 각하고 나오는 각하고
       같습니다.
                                    
       (그림)                     법선n
                                   |
                             i     |     .o
                               .   |   .
                                 . | .
                        -------------------------당구벽
        당구공이 벽과 부딪혀서 들어갈때 각은 법선과 당구공 방향 벡터와 내적으로
        구할수 있습니다. 결국 이 내적의 두배의 각도로 튕켜져 나옵니다.

        들어가는 공의 방향 벡터가 i라고 하고
        법선을 n, 튕겨져 나올 방향 벡터가 o라고 했을때

        o = 2(i*n)n - i; 란 벡터 공식이 나옵니다.
        (이것도 어떻게 나왔나고 뭍지 마세요..그냥 책에 있음)

        식이 좀 어렵죠..

        c/c++로 풀이하면..

        //  여기서 쓰인 벡터는 노말 벡터로 미리 만들어 줘야 합니다.

        //  일단 i 벡터의 방향을 뒤 집고..
        i_x = -i_x;
        i_y = -i_y;

        rad = 2 * ( n_x * i_x + n_y * i_y );
        o_x = rad * n_x - i_x;
        o_y = rad * n_y - i_y;

        이것도 신기하져.. 이걸 잘 응용하면 당구 겜도 만들어요..

        (공하고 부딪힐때는 공끼리 부짖히는 점을 법선 벡터로 두면..)

        잠시 삼천포로 빠졌군요..
      결국 빛을 표면에 뿌릴때 반사되는 각도를 계산하기 위해서 필요한거져..


        위의 예제는 2개의 벡터를 가지고 예기 했지만 3D 그래픽에서는
        버텍스가 3개입니다. 그럼.. 점 하나를 기준으로 (0,0,0)으로
        이동 시켜 버려면 2개만 가지고 위의 외적으로 노말을 구할 수 있습니다.

        다음은 노말벡터을 구하는 예제입니다.
        물론 연산에 들어가지전 단위 벡터로 만드는 건 잊지 마세요
      
        v1[0] = v0[0] - v1[0];
        v1[1] = v0[1] - v1[1];
        v1[2] = v0[2] - v1[2];
        v2[0] = v1[0] - v2[0];
        v2[1] = v1[1] - v2[1];
        v2[2] = v1[2] - v2[2];

        result[0] = v1[1] * v2[2] - v1[2] * v2[1];
        result[1] = v1[2] * v2[0] - v1[0] * v2[2];
        result[2] = v1[0] * v2[1] - v1[1] * v2[0];

        대충 아시겟져...
        이것만 알면 3D 그래픽에 꼭 핵심적인 벡터 연산은 아신겁니다.


        추신: 벡터는 참 신기하져? 도강이라도 하세요..

        iMusicSoft 주임 연구원 이태경 ( 저 대전 살아요..)

    ------------------------------------------------------------------------------
    벡터 내적강좌중 . 틀린부분이 있습니다..
    바로 각도를 구하는부분에서 틀리셔떠군ㅇ -_- 빨리 쓰시느라 그러션나..
    theta=a_x*b_x+a_y*b_y; 여기부분부터 미테까지
    쭈욱 툴려씁니다...
    그게 아니라 그부분엔  costheta=a_x*b_x+a_y*b_y; 라고 구한다음
    theta=acos(costheta); 한다음  theta=theta/3.141592654*180; 해야지
    제대로된 각이나오죠.. -_-; 백터내적을 구하면 코사인(세타) 값이나오는데
    이걸 그냥 cos(코사인(세타)) 로하신격이된네여...
    소스로 다시쓰자면요

    float a_x, a_y, b_x, b_y;
    float v, costheta, theta;

       a_x = 1.0;    a_y = 3.0;
       b_x = 3.0;    b_y = 1.0;
       // 먼저 단위 벡터로 만든다.
       v = sqrt(a_x * a_x + a_y * a_y);
       a_x /= v;
       a_y /= v;
       v = sqrt(b_x * b_x + b_y * b_y);
       b_x /= v;                                                                  

       b_y /= v;
       costheta = a_x*b_x + a_y*b_y ;
       theta = acos(costheta);
       theta = theta/3.141592654*180;
    가 맞는 소스 입니다 ^.^; 그럼..

    댓글