CAFE

3D 게임 프로그래밍

3강. Vertices (정점)

작성자나우프로|작성시간16.07.01|조회수1,819 목록 댓글 1

 부글부글유튜브 동영상 강의 주소

(1) https://youtu.be/ExFyJAJMuKo

(2) https://youtu.be/ZFOnOHiwprI

(3) https://youtu.be/xYDzdQAUF8E

(4) https://youtu.be/cPFikK8_GFs

(5) https://youtu.be/kSTE0_q6p9M

(6) https://youtu.be/RZLiP3t_D48


소스는 자료실에 있습니다. !!

 

3.1 좌표계


좌표계는 크게 2D 좌표계와 3D 좌표계로 나눌 수 있다.

 

2D 좌표계는 모니터 좌표계와 동일하며 x, y 좌표를 가진다.

3D 좌표계는 공간상의 한 점을 나타내기 위해 x, y, z 좌표로 위치를 나타낸다.

3D 프로그래밍은 3D 좌표계를 사용하며 Direct3D는 3D 공간상의 정점들을 빠르게 2D 좌표계로 바꾸어 출력하게 된다.


[그림 3-1] 3D 좌표계 (왼손 좌표계)


3.2 벡터


공간상의 한 점을 나타낼 때 x, y, z 좌표를 벡터라고 한다.

즉 원점을 시점으로 공간상의 어느 위치에 있는 좌표를 말한다.

벡터는 좌표 개념외에 크기(길이)를 가지는데 이 크기는 시점으로부터 얼마간 떨어진 거리를 나타낸다.

수학에서 벡터는 아래와 같이 나타내며 x, y, z를 의 성분 또는 요소값이라 한다.



(1) 벡터 구조체


Direct3D에서는 벡터를 다룰 수 있는 D3DVECTOR 구조체와 D3DXVECTOR3 구조체를 제공한다. D3DXVECTOR3는 벡터를

연산할 수 있는 연산자 오버로딩이 제공된다.


typedef struct _D3DVECTOR {

  float x;

  float y;

  float z;

} D3DVECTOR;

 

 

typedef struct D3DXVECTOR3 : public D3DVECTOR

{

public:

    D3DXVECTOR3() {};

    D3DXVECTOR3( CONST FLOAT * );

    D3DXVECTOR3( CONST D3DVECTOR& );

    D3DXVECTOR3( CONST D3DXFLOAT16 * );

    D3DXVECTOR3( FLOAT x, FLOAT y, FLOAT z );

 

    // casting

    operator FLOAT* ();

    operator CONST FLOAT* () const;

 

    // assignment operators

    D3DXVECTOR3& operator += ( CONST D3DXVECTOR3& );

    D3DXVECTOR3& operator -= ( CONST D3DXVECTOR3& );

    D3DXVECTOR3& operator *= ( FLOAT );

    D3DXVECTOR3& operator /= ( FLOAT );

 

    // unary operators

    D3DXVECTOR3 operator + () const;

    D3DXVECTOR3 operator - () const;

 

    // binary operators

    D3DXVECTOR3 operator + ( CONST D3DXVECTOR3& ) const;

    D3DXVECTOR3 operator - ( CONST D3DXVECTOR3& ) const;

    D3DXVECTOR3 operator * ( FLOAT ) const;

    D3DXVECTOR3 operator / ( FLOAT ) const;

 

    friend D3DXVECTOR3 operator * ( FLOAT, CONST struct D3DXVECTOR3& );

 

    BOOL operator == ( CONST D3DXVECTOR3& ) const;

    BOOL operator != ( CONST D3DXVECTOR3& ) const;

 

} D3DXVECTOR3, *LPD3DXVECTOR3;

[표 3-1]


(2) 벡터 연산


1) 벡터의 합

벡터의 합은 아래 [그림 1.5]와 같이 , 벡터의 시작점에서부터  , 벡터가 맞닿는 점까지 그은 선분을

벡터의 합 ( )이라고 한다.



[그림 3.2]


벡터형(type)이 D3DXVECTOR3인 경우에는 + 연산자 오버로딩을 활용할 수도 있고 아래의 함수를 이용해도 된다.


D3DXVECTOR3 * D3DXVec3Add(

  __inout  D3DXVECTOR3 *pOut,

  __in     const D3DXVECTOR3 *pV1,

  __in     const D3DXVECTOR3 *pV2

);

 

 헤더 : D3dx9math.h  라이브러리 : D3dx9.lib


콘솔 환경에서 벡터의 합을 출력하는 프로그램을 작성하면 아래와 같다.


#include "stdafx.h"

#include <d3dx9math.h>

 

int _tmain(int argc, _TCHAR* argv[])

{

    D3DXVECTOR3 v1( 0.0f, 3.0f, 0.0f );

    D3DXVECTOR3 v2( 3.0f, 0.0f, 0.0f );

    D3DXVECTOR3 v3;

 

    v3 = v1 + v2;

 

    printf( "합 : %f %f %f \n",  v3.x, v3.y, v3.z );

 

    D3DXVec3Add( &v3, &v1, &v2 );

    printf( "합 : %f %f %f \n",  v3.x, v3.y, v3.z );

    return 0;

}

[소스 3-1]


(3) 벡터의 뺄셈


두 벡터의 뺄셈은 로 나타낸다.

벡터 뺄셈을 하는 경우에 항상 생각 하여야 할 것은 벡터의 연산에서는 시작점과 끝점은 고려치 않는다는

것과 벡터의 -(음부호)는 역벡터로써 원래 벡터의 반대 방향을 의미한다는 것이다. 벡터의 뺄셈 연산의 의미를

살펴보면 [그림 3.3]과 같이 와  방향의 반대 방향인 벡터와의 합인 이다. 

[그림 3.3] 벡터 뺄셈

벡터형(type)이 D3DXVECTOR3인 경우에는 – 연산자 오버로딩을 활용할 수도 있고 아래의 함수를 이용해도 된다.


D3DXVECTOR3 * D3DXVec3Subtract(

  __inout  D3DXVECTOR3 *pOut,

  __in     const D3DXVECTOR3 *pV1,

  __in     const D3DXVECTOR3 *pV2

);

 

 헤더 : D3dx9math.h  라이브러리 : D3dx9.lib


콘솔 환경에서 벡터의 뺄셈을 출력하는 프로그램을 작성하면 아래와 같다.


#include "stdafx.h"

#include <d3dx9math.h>

 

int _tmain(int argc, _TCHAR* argv[])

{

    D3DXVECTOR3 v1( 0.0f, 3.0f, 0.0f );

    D3DXVECTOR3 v2( 30f, 0.0f, 0.0f );

    D3DXVECTOR3 v3;

 

    v3 = v1 + v2;

 

    printf( "뺄셈 : %f %f %f \n",  v3.x, v3.y, v3.z );

 

    D3DXVec3Add( &v3, &v1, &v2 );

    printf( "뺄셈 : %f %f %f \n",  v3.x, v3.y, v3.z );

    return 0;

}

[소스 3-2]


(4) 벡터의 크기

 

의 성분이 로 구성되어 있는 경우에 벡터의 크기를 구하여 보자.

참고적으로 벡터의 크기는 양적인 개념이기에 구한 값이 벡터가 아니라 값(스칼라)이라는 것을 알 수 있다.

[그림 3.4]


[그림 3.4]에서 를 자세히 보면 밑변이 , 높이가 인 직각 삼각형이라는 것을 알 수 있다.



[그림 3.5]


그래서 [그림 3.5]와 같이 피타고라스의 정리를 적용하면 빗변의 크기인 의 크기를 구할 수 있다.

최종적으로 의 크기 즉 길이는 아래의 공식으로 구하게 된다.



그리고 의 크기는 의 좌우에 ‘|’ 을 붙여서 로 나타낸다.

벡터의 크기는 아래의 함수를 통해 구하면 된다.

 

FLOAT  D3DXVec3Length(

  __in  const D3DXVECTOR3 *pV

);

 

 헤더 : D3dx9math.h  라이브러리 : D3dx9.lib


위의 함수를 이용하여 벡터 ( 3.0f, 3.0f, 0.0f )의 크기를 구하면 아래와 같다.


#include "stdafx.h"

#include <d3dx9math.h>

#include <math.h>

 

int _tmain(int argc, _TCHAR* argv[])

{

    D3DXVECTOR3 v4( 3.0f, 3.0f, 0.0f );

    float fLength;

 

    fLength = D3DXVec3Length( &v4 );

    printf( "크기 : %f\n", fLength );

    return 0;

}


(5) 값과 벡터의 곱셈 (벡터의 크기 변환)


벡터에 값을 곱하는 것은 벡터의 요소에 값이 곱하는 것이며 만일 값이 양수라면 벡터의 방향에는 영향을

주지 않고 크기에 영향을 준다.


2 × ( 2, 1 ) = ( 4, 2 )


아래 [그림 3-7]은 [그림 3-6]의 벡터에 값을 곱했을 때 결과이다.

방향은 동일하나 크기만 차이가 있음을 알 수 있으며 만약 곱해지는 값이 음수(-)라면 현재 [그림 3-7]과

같은 크기를 가지지만 벡터의 방향이 바뀐 결과를 얻게 된다.

즉 벡터에서 부호는 방향을 결정하는 역할을 한다.


( 2, 1 )

2 * ( 2, 1 ) = ( 4, 2 )

[그림 3-6]

[그림 3-7]


값(스칼라)과 벡터를 곱해 주어 벡터의 크기를 변화시키는 함수는 아래와 같다.

 

D3DXVECTOR3 * D3DXVec3Scale(

  __inout  D3DXVECTOR3 *pOut,

  __in     const D3DXVECTOR3 *pV,

  __in     FLOAT s

);

 

 헤더 : D3dx9math.h  라이브러리 : D3dx9.lib


 #include "stdafx.h"

#include <d3dx9math.h>

#include <math.h>

 

int _tmain(int argc, _TCHAR* argv[])

{

    float fScale = 2.0f;

    D3DXVECTOR3 v5( 2.0f, 1.0f, 0.0f );

 

    D3DXVec3Scale( &v5, &v5, fScale );

    printf( "값과 벡터의 곱 : %f %f %f \n", v5.x, v5.y, v5.z );

 

    return 0;

}


(6) 역벡터


역벡터란? 크기는 같으나 방향이 서로 반대인 벡터를 말한다.

역벡터를 우리가 표현 할 때에는 [그림 3-8]과 같이 벡터 앞에 -(음부호)를 붙여서 표시를 한다.

이때 -(음부호)의 역할은 벡터의 방향을 바꾸는 역할을 하며 각 성분에 -1을 곱한 것과 같다.

예로써 ( 1, 1 ) 벡터에 -1을 벡터 성분에 곱해 주면 크기는 그대로지만 방향이 반대가 된다는 것을

아래의 [그림 3-9]을 통해 알 수 있다.


 [그림 3-7]

[그림 3-8]



(7) 단위 벡터 (normalized vector , 정규화 벡터)


벡터의 크기가 1인 벡터를 단위 벡터 또는 정규화 벡터라고 한다.

우리가 알고 있는 곱셈 개념 중에서 1 × n = n일 때 n에 어떤 수를 넣더라도 항상 n이 나오도록 하는

1을 곱셈에서의 항등수라고 한다.

벡터에서도 값과 벡터가 곱해질 때 단위벡터는 항등수에 해당이 된다. 단위 벡터는 크기가 1이고 방향을

가지고 있으므로 여기에 값을 곱해 주면 방향은 같지만 크기가 다른 벡터를 마음대로 만들 수 있다.

그래서 어떤 벡터의 방향을 얻고자 할 때는 벡터의 단위 벡터를 구하면 된다.

단위 벡터를 구하는 식은 아래와 같으며 단위 벡터는 일반적으로 로 표시 한다.



단위벡터를 구해 주는 함수는 아래와 같으며 이 함수를 이용하여 단위벡터를 구한 후에 크기를 출력하면

아래 소스와 같다.

 

 D3DXVECTOR3 * D3DXVec3Normalize(

  __inout  D3DXVECTOR3 *pOut,

  __in     const D3DXVECTOR3 *pV

);

 

 헤더 : D3dx9math.

라이브러리 : D3dx9.lib

 

#include "stdafx.h"

#include <d3dx9math.h>

 

int _tmain(int argc, _TCHAR* argv[])

{

    D3DXVECTOR3 v6( 2.0f, 2.0f, 3.0f );

    D3DXVECTOR3 vResult;

    float fNormalize;

 

    D3DXVec3Normalize( &vResult, &v6 );

    printf( "단위벡터 : %f %f %f \n", vResult.x, vResult.y, vResult.z );

                                   fNormalize = D3DXVec3Length( &vResult );

    printf( "크기: %f \n", fNormalize );

    return 0;

}

 

3.3 내적 (the dot-product)

 


(1) 내적의 개념


내적은 하나의 약속이며 정의이다.

그리고 내적은 두 벡터 사이의 연산이지만 결과는 벡터가 아닌 값이 나오는데 이 값은 두 벡터의 사이각을

알기 위해서도 사용되며 차후에 설명하겠지만 투영된 길이를 알기 위해서도 사용된다.

  

내적 공식은 아래의 [식 3.1], [식 3.2]와 같다.


                    [식 3.1]

             [식 3.2]


[식 3.1]은 두 벡터의 각과 크기를 알고 있을 경우에 사용할 수 있는 공식이다.

[식 3.2]는 두 벡터의 각은 모르고 벡터의 성분만을 알고 있는 경우 사용할 수 있는 공식이다.


(2) 벡터의 내적 활용


벡터의 내적을 활용하는 때는 아래 3가지이다.


첫째, 두 벡터의 사이각을 알기 위해 사용된다.

이때는 두 벡터의 크기와 성분(x, y, z)을 알고 있는 경우로써 위의 내적 [식 3.1]에서 를 구하면

그 사이각을 알 수 있다.

아래의 [식 3.3]은 그 유도 과정이다.


    [식 3.3]


둘째, [그림 3-9]와 같이 에서 수직으로 내린 길이를 구할 때 사용하는데 이 길이를 투영길이라고 한다.

투영길이는 아래 [그림 3-9]와 같으므로 이 길이를 구할 때는 의 단위벡터를 적용하면 된다. 

의 단위벡터 길이는 1이며 이 값은 항등수이므로 내적 공식에 적용하면 이 되어

결국 가 된다.

또는 에서 의 단위벡터를 적용하면 된다.


    

 [그림 3-9]


셋째, 두 벡터가 같은 방향을 가리키는지를 내적으로 알 수 있다.

[식 3.1]에서 값이 0이면 90도와 270도이므로 두 벡터는 수직이다.

값이 1 이면 사이각이 0이므로 같은 방향이며 -1이면 180도로써 서로 반대 방향이다.

이와 같이 각도의 값으로 두 벡터의 방향을 알게 된다.

 

0

30

45

60

90

180

270

360

1

0

-1

0

1

[도표 3-1] 코사인값

 

[그림 3-10] 코사인 곡선

 

(3) 내적 함수


 FLOAT  D3DXVec3Dot(

  __in  const D3DXVECTOR3 *pV1,

  __in  const D3DXVECTOR3 *pV2

);

 

 헤더 : D3dx9math.h  라이브러리 : D3dx9.lib


위의 함수를 이용하여 두 벡터의 사이각을 알아내는 프로그램을 작성하면 아래와 같다.


#include "stdafx.h"

#include <d3dx9math.h>

 

 

int _tmain(int argc, _TCHAR* argv[])

{

    D3DXVECTOR3 v7( 3.0f, 0.0f, 0.0f );

    D3DXVECTOR3 v8( -3.0f, 0.0f, 0.0f);

    float fCos, fDot, fScale;

 

    fDot = D3DXVec3Dot( &v7, &v8 );

    fScale = D3DXVec3Length( &v7 ) * D3DXVec3Length( &v8 );

    fCos = fDot / fScale;

    printf( "라디안 : %f \n", fCos );

    return 0;

}

 


3.4 외적


(1) 외적의 개념


외적은 두 벡터가 이루는 평면의 수직 벡터를 구하기 위한 것이므로 결과는 벡터가 된다.


[그림 3-11] 외적 벡터


위의 [그림 3-11]의 을 외적 또는 법선 벡터라고 하며 일반적으로 법선 벡터라는 용어를 많이 사용한다.

외적 공식은 아래와 같다.


위의 식을 자세히 살펴보면 은 방향 벡터이고 나머지는 값이기에 결과는 벡터라는 것을 알 수 있다.

그리고 Direct3D는 왼손좌표계를 사용하므로 왼손법칙에 따라 그 방향을 계산하지 않고도 미리 알아 낼 수 있다.

왼손 법칙을 이용하여 법선 벡터의 방향을 알아내는 방법을 살펴보면 , 가 [그림 3-11] 과 같다면

손가락으로 맞추고 를 손바닥으로 감으면 엄지손가락이 가리키는 방향이 바로 외적인 법선 벡터의 방향이 된다.


(2) 외적의 활용


외적을 통해서 알 수 있는 것은 다음과 같다.


첫째, 두 벡터로 이뤄진 한 평면이 앞면인지 뒷면인지를 알 수 있다.

 

[그림 3-12]

[그림 3-13]

[그림 3-13]과 같이 법선 벡터가 한 평면의 뒤를 가리킨다면 뒷면은 볼 수 없는 부분이므로  화면에 출력에서

제외되는 면이 된다.

이와 같이 화면 출력에서 제외시키는 것을 컬링(Culling)이라고 하는데 Direct3D에서는 컬링(Culling)옵션에

따라 출력에서 제외시키거나 출력하게도 할 수 있다.

이때 컬링의 기준이 되는 것이 바로 이 법선 벡터의 방향이다.


둘째, 빛의 방향과 평면의 법선사이의 각도를 이용하여 평면에 적용될 빛의 영향을 결정하게 된다.

그래서 빛을 평면에 비추고자 할 때 평면을 이루는 각 벡터 정보에는 법선 벡터에 대한 정보가 반듯이 있어야 한다.


셋째, 컬링(Culling)을 할 때 사용된다.


(3) 외적의 교환 법칙


외적은 교환 법칙이 성립하지 않으므로 는 서로 반대 방향을 가리키게 된다.

참고적으로 는 동일한 방향인데 이것은 -(음부호)가 방향을 바꾸기 때문이다.

[그림 3-14]를 참고하면서 살펴보기 바란다.

 

[그림 3-14]


(4) 외적 함수


D3DXVECTOR3 * D3DXVec3Cross(

  __inout  D3DXVECTOR3 *pOut,

  __in     const D3DXVECTOR3 *pV1,

  __in     const D3DXVECTOR3 *pV2

);

 

 헤더 : D3dx9math.h  라이브러리 : D3dx9.lib


위의 함수를 이용하여 두 벡터의 법선 벡터를 구해보면 아래와 같다.


#include "stdafx.h"

#include <d3dx9math.h>

 

int _tmain(int argc, _TCHAR* argv[])

{

    D3DXVECTOR3 v1( 3.0f, 0.0f, 0.0f );

    D3DXVECTOR3 v2( 0.0f, 3.0f, 0.0f );

    D3DXVECTOR3 vResult;

 

    D3DXVec3Cross( &vResult, &v1, &v2 );

    D3DXVec3Normalize( &vResult, &vResult );

    printf( "%f %f %f \n", vResult.x, vResult.y, vResult.z );

    return 0;

}

 


3.5 Direct3D 정점

 

(1) 정점의 구조


Direct3D에서는 유연한 정점 포맷이라고 해서 프로그래머가 정점의 최적 정보만을 가질 수 있도록 그 형식을 설정할 수

있다. 정점에 포함되는 정보는 [그림 3-15]에 나와 있으며 구조체 형식은 다음과 같다.



[그림 3-15]


정점 구조체의 내용과 정의는 아래와 같다.


  ① 1단계 : float x, y, z 좌표값

  ② 2단계 : float rhw ( 이미 변환된 정점에만 사용하는 옵션 )

  ③ 3단계 : 블랜딩 값

  ④ 4단계 : 정점 법선

  ⑤ 5단계 : 정점 포인트의 크기

  ⑥ 6단계 : diffuse 값

  ⑦ 7단계 : specular 색

  ⑧ 8단계 : 1 ~ 8까지 텍스쳐 좌표쌍 (u, v  float 값 )


이와 같은 정점 순서로 정점을 구성하여야 한다. 주의할 점은 구조체의 멤버 변수의 순서가 바뀌면 렌더링이 않되므로

순서를 주의하여 정의해야 한다.

정점의 구조체가 어떤 정보를 포함하는지에 대한 것은 아래의 값을 비트 OR 연산하여 설정한다.

 

D3DFVF_DIFFUSE, D3DFVF_NORMAL, D3DFVF_SPECULAR, D3DFVF_XYZ,

D3DFVF_XYZRHW, D3DFVF_XYZW, D3DFVF_TEX0~ D3DFVF_TEX7


정점의 위치와 색상을 정의한 예를 살펴보자.


struct CUSTOMVERTEX

{

    FLOAT x, y, z;

    DWORD color;

};

 

#define D3DFVF_CUSTOMVERTEX   ( D3DFVF_XYZ | D3DFVF_DIFFUSE )


위 정점의 형식은 x, y, z의 위치값과 정점의 색상을 한 개의 정점에 포함 시킨 것이다.

정점의 형식은 위와 같이 #define을 통해 정의한 D3DFVF_CUSTOMVERTEX 으로 설정하게 된다.


정점 형식에서 rhw의 설정은 현재의 정점을 2D 좌표처럼 사용하는 것을 말한다. 차후에 알게 되겠지만 rhw가

설정되면 현재의 정점 좌표가 3D 변환인 월드, 뷰, 투영변환이 적용된 최종 좌표이므로 x, y 좌표만을 참조하여

그대로 화면에 출력하게 된다. 일반적으로 w의 값은 1.0f으로 고정하여 사용한다.

정점 정보 설정의 여러 예를 살펴보면 다음과 같다.

 

struct CUSTOMVERTEX

{

       D3DXVECTOR3 position;

       D3DXVECTOR3 normal;

       DWORD       diffuse;

       float tu, tv;

};

 

#define D3DFVF_CUSTOMVERTEX

           ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1 )

 

struct CUSTOMVERTEX

{

       FLOAT x, y, z ;

       DWORD    diffuse; 

}

 

#define D3DFVF_CUSTOMVERTEX ( D3DFVF_XYZ | D3DFVF_DIFFUSE )

 

struct CUSTOMVERTEX

{

       FLOAT x, y, z, rhw;

       DWORD color;      

};

 

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)


(2) 정점의 생성과 초기화


정점을 생성한다는 것은 정점 구조체에 맞춰 정의와 데이터를 설정하는 것을 말한다.

아래의 예는 삼각형을 출력하기 위해서 정점의 구조와 데이터를 설정하는 내용이다.

 

struct CUSTOMVERTEX

{

    FLOAT x, y, z, rhw; // The transformed position for the vertex.

    DWORD color;        // The vertex color.

};

 

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)

 

CUSTOMVERTEX vertices[] =

{

    { 150.0f,  50.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color

    { 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00, },

    {  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff, },

};


Direct3D는 왼손좌표계를 사용하므로 정점의 순서는 시계방향(CW)으로 입력이 되어야 한다.

이렇게 해주는 이유는 시계방향으로 입력이 되어야 삼각형의 면의 법선벡터가 앞으로 나타내기 때문이다.

만약 반시계방향(CCW)로 한다면 컬링되어 출력에서 제외된다.

  


[그림 3-16]


3.6 정점 버퍼

 

출력할 정점을 저장하는 버퍼를 정점 버퍼라고 한다.

Direct3D에서는 정점의 위치값으로 직접 출력하는 것이 아니라 출력할 정점을 모두 정점 버퍼에 넣은 후에

정점 버퍼 안에 있는 정점을 한꺼번에 출력한다.


(1) 정점 버퍼의 생성


정점 버퍼는 다음과 같은 함수를 통해 생성된다.

HRESULT CreateVertexBuffer(

  [in]           UINT Length,

  [in]           DWORD Usage,

  [in]           DWORD FVF,

  [in]           D3DPOOL Pool,

  [out, retval]  IDirect3DVertexBuffer9 **ppVertexBuffer,

  [in]           HANDLE *pSharedHandle

);


먼저 Length은 정점 구조체의 크기를 설정하여 준다.

Usage에는 항상 0으로 설정하며 FVF에는 정점 구조에 대한 값인 D3DFVF_CUSTOMVERTEX 가 설정된다.

 Pool은 정점을 메모리에 생성하는 방법을 설정하는 것으로 아래의 도표에 나온 값으로 설정하되

현재는 기본형인 D3DPOOL_DEFAULT를 설정한다.


D3DPOOL_DEFAULT

최적의 메모리에 위치시킨다.

최적의 위치는 비디오 메모리와 시스템 메모리 둘 중의 하나이나 주주로 비디오 메모리에 생성한다. 만약 device를 잃어버린 경우에는 다시 정점버퍼를 생성하여야 한다.

D3DPOOL_SYSTEMMEM

시스템 메모리에 위치시킨다.

Device를 읽어 버려도 다시 정점 버퍼를 생성할 필요가 없다.

D3DPOOL_MANAGED

Direct3D가 정점버퍼를 시스템 메모리나 그래픽 메모리에 놓이게

할지 알아서 관리를 하며 device를 잃어 버려도 다시 정점 버퍼를 생성할 필요가 없다.


정점 버퍼의 객체형은 LPDIRECT3DVERTEXBUFFER9이므로 이 포인터 변수의 메모리 주소를 설정한다.

pSharedHandle는 아직 사용되지 않는 예비 변수이므로 NULL을 입력한다.

정점 버퍼의 생성 소스는 다음과 같다.


LPDIRECT3DVERTEXBUFFER9 pVB; 

 

if( FAILED( pd3dDevice->CreateVertexBuffer( 3*sizeof(CUSTOMVERTEX), 0 , 

                       D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &pVB, NULL ) ) )

    return E_FAIL;


(2) 정점을 정점 버퍼에 복사


정점 버퍼를 사용하기 위해서는 먼저 정점 버퍼의 메모리를 Lock()하여 독점한다.

Lock()을 하게 되면 해당 메모리는 다른 자원에서 접근할 수 없게 되며 정점을 저장하기 위한 시작 메모리주소를

얻게 된다.

이 메모리 주소를 시작으로하여 선언한 정점을 memcpy()를 통해 복사하게 된다.

정점 복사가 다 끝난 후에는 UnLock()을 해준다.

Lock()은 IDirect3DVertexBuffer9 클래스에서 제공하는 멤버 함수이다.

 

HRESULT Lock(

  [in]   UINT OffsetToLock,

  [in]   UINT SizeToLock,

  [out]  VOID **ppbData,

  [in]   DWORD Flags

);


VOID* pVertices;

if( FAILED( pVB->Lock( 0, sizeof(vertices), (void**)&pVertices, 0 ) ) )

    return E_FAIL;

 

memcpy( pVertices, vertices, sizeof(vertices) );

 

pVB->Unlock();



[그림 3-17]


3.7 정점 렌더링


Direct3D에서 렌더링을 하는 기본 구조는 device->BeginScene()와 device->EndScene() 사이에 정점의 렌더링

코드를 작성하면 된다.

아래의 그림은 소스의 처리 순서를 그림으로 나타낸 것이다.


[그림 3-18]


(1) 렌더링하기 위한 정점 버퍼의 설정


정점을 렌더링하기 위해서는 정점 버퍼와 정점의 포맷을 d3d device에게 알려 주어야 한다.

이와 같은 부분을 해주는 함수가 바로 SetStreamSource() 이며 d3d device 객체의 멤버 함수이다.


HRESULT SetStreamSource(

  [in]  UINT StreamNumber,

  [in]  IDirect3DVertexBuffer9 *pStreamData,

  [in]  UINT OffsetInBytes,

  [in]  UINT Stride

);


위 [그림 3-18]을 보면 여러개의 스트림으로 정점 버퍼를 설정할 수 있는데 0부터 시작하므로 StreamNumber에는

0을 설정한다. Stride에는 정점 구조체의 크기를 바이트 단위로 설정하므로 sizeof(CUSTOMVERTEX)가 된다.

 

pd3dDevice->SetStreamSource( 0, pVB, 0, sizeof(CUSTOMVERTEX) );


(2) d3d device에 정점 구조를 설정


정점 구조가 어떻게 되는지를 device에 설정을 해야 하며 IDirect3DDevice9::SetFVF()가 그 역할을 한다.


HRESULT SetFVF(

  [in]  DWORD FVF

);


위의 함수의 설정은 다음과 같다.

 

pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );


(3) 정점 출력


정점 출력을 할 수 있게 해 주는 함수로 IDirect3DDevice9::DrawPrimitive()가 있다.

 

HRESULT DrawPrimitive(

  [in]  D3DPRIMITIVETYPE PrimitiveType,

  [in]  UINT StartVertex,

  [in]  UINT PrimitiveCount

);


위 함수는 정점을 출력할 때 어떤 형식으로 출력할 것인지를 설정해야하며 PrimitiveType에  따라 다음과

같은 출력을 한다.


① 포인트 리스트(point list)

 

포인트 리스트는 점 단위로 출력하는 방식을 말하며 아래 [그림 3-19]와  같다.


[그림 3-19]


struct CUSTOMVERTEX

{

    float x, y, z;

};

 

CUSTOMVERTEX Vertices[] =

{

    {-5. 0, -5. 0, 0.0},  { 0.0,  5.0, 0.0},

    { 5.0, -5. 0, 0.0},   {10.0,  5.0, 0.0},

    {15.0, -5. 0, 0.0},   {20.0,  5.0, 0.0}

};

 

d3dDevice->DrawPrimitive( D3DPT_POINTLIST, 0, 6 );

      

포인트 리스트는 한 개의 정점을 기본 단위로 하므로 PrimitiveCount에 6을 설정한다.


② 라인 리스트 (line list)


라인 리스트는 선분을 단위로 한다. 라인 리스트는 3D 장면에서 소나기와 같은 비(rain)를 표현 할 때

용이하게 사용된다.

[그림 3-20]


struct CUSTOMVERTEX

{

    float x, y, z;

};

 

CUSTOMVERTEX Vertices[] =

{

    {-5. 0, -5. 0, 0.0},  { 0.0,  5.0, 0.0}, { 5.0, -5. 0, 0.0}, {10.0,  5.0, 0.0},

    {15.0, -5. 0, 0.0},  {20.0,  5.0, 0.0}

};

 

d3dDevice->DrawPrimitive( D3DPT_LINELIST, 0, 3 );


라인 리스트는 정점 두 개가 하나의 선분을 이루므로 위의 경우 PrimitiveCount의 개수는 3이 된다.


③ 라인 스트립(line strip)


라인 스트립이란 선분들을 연속적으로 연결하여 출력하는 방식이다.

라인 스트립을 이용하면 닫히지 않은 다각형을 생성할 수도 있다.

[그림 3-21]과 같이 출력할 때 출력의 기본 단위는 선분이 되므로 PrimitiveCount에는 5가 설정된다.



[그림 3-21]


struct CUSTOMVERTEX

{

    float x, y, z;

};

 

CUSTOMVERTEX Vertices[] =

{

    {-5. 0, -5. 0, 0.0}, { 0.0,  5.0, 0.0}, { 5.0, -5. 0, 0.0},

    {10.0,  5.0, 0.0}, {15.0, -5. 0, 0.0}, {20.0,  5.0, 0.0}

};

 

d3dDevice->DrawPrimitive( D3DPT_LINESTRIP, 0, 5 );


④ 삼각형 리스트 (triangle list)


삼각형 리스트는 정점을 삼각형 단위로 출력하는 것을 말한다.
삼각형 리스트는 적어도 3개의 정점이 필요하며 정점의 총 개수도 3 배수가 되어야만 한다.

[그림 3-22]


struct CUSTOMVERTEX

{

    float x, y, z;

};

 

CUSTOMVERTEX Vertices[] =

{

    {-5. 0, -5. 0, 0.0}, {0.0,  5.0, 0.0}, { 5.0, -5. 0, 0.0},

    {10.0,  5.0, 0.0}, {15.0, -5. 0, 0.0}, { 20.0,  5.0, 0.0}

 

};

 

d3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 2 );


⑤ 삼각형 스트립 (triangle strip)

삼각형 스트립은 삼각형을 연결하여 출력하는 방식을 말한다. 만약 아래의 [그림 3-23]과 같이 삼각형만으로
출력한다면 정점은 12개가 있어야 하지만 삼각형 스트립은 정점을 연결하여 삼각형을 출력하므로 6개의 정점만
있으면 된다.

[그림 3-23]


struct CUSTOMVERTEX

{

    float x, y, z;

};

 

CUSTOMVERTEX Vertices[] =

{

    {-5. 0, -5. 0, 0.0}, { 0.0,  5.0, 0.0}, { 5.0, -5. 0, 0.0},

    {10.0,  5.0, 0.0}, {15.0, -5. 0, 0.0}, {20.0,  5.0, 0.0}

};

 

d3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 4 );

 
삼각형 스트립은 삼각형의 정점 2개와 새로운 정점 1개를 연결하여 첫 번째 삼각형을 구성한 후에 두 번째

삼각형은 한 개의 정점만 있으면 된다.

즉 첫 번째 삼각형의 정점이 v0, v1, v2 였다면 새로운 정점 v3를 추가하면 두 번째 삼각형이 구성된다.


이와 같이 출력하면 v0, v1, v2는 시계(CW)방향이지만 v1, v2, v3는 반시계(CCW)방향이 된다.

이때 왼손좌표계에서 정점의 순서가 시계방향(CW)은 앞면으로 보지만 반시계(CCW)방향은 뒷면으로 인식하여

컬링의 대상이 된다.

하지만 삼각형 스트립일 때는 처음 삼각형의 정점 순서를 적용하므로 처음 삼각형을 이루는 정점의 순서만

시계방향이면 된다.


⑥ 삼각형 팬(triangle fan)


삼각형 팬은 모든 삼각형이 1 개의 정점을 공유하면서 삼각형을 연결하여 출력하는 형식을 말한다.

첫 정점을 공유하면서 출력하므로 아래 [그림 3-24]의 경우에는 ( 0, 0, 0 )을 공유하고 있다.

[그림 3-24]


struct CUSTOMVERTEX

{

        float x,y,z;

};

 

CUSTOMVERTEX Vertices[] =

{

        { 0.0, 0.0, 0.0},

        {-5.0, 5.0, 0.0},

        {-3.0,  7.0, 0.0},

        { 0.0, 10.0, 0.0},

        { 3.0,  7.0, 0.0},

        { 5.0,  5.0, 0.0},

};

 

d3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 4 );

 

3.2 에서 설정한 정점을 출력하는 코드는 아래와 같다.


pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );


3.8 정점 버퍼와 Direct3D의 해제


정점 버퍼를 해제할 때는 d3d device를 해제하기 이전에 해야 한다.

왜냐하면 정점 버퍼도 d3d device를 통해 생성된 것이기 때문이다.

참고적으로 Direct3D에서는 해제에 관한 대부분의 멤버 함수가 Release()되어 있다.


VOID Cleanup()

{

      if( pVB != NULL )       

         pVB->Release();

 

      if( pd3dDevice != NULL )

         pd3dDevice->Release();

 

      if( pD3D != NULL )      

         pD3D->Release();

}


[실행 결과]




3.9 프레임워크 안에서 프로그래밍하기

 

2강에서 제작한 프레임워크에 삼각형을 출력하는 코드를 적용해 보자.

CGameEdu01 클래스에 위의 코드들을 직접 작성할 수도 있지만 CTriangle 클래스를 만들어 CGameEdu01 클래스에

적용할 수도 있다.

이 두 가지의 예를 살펴보면서 장단점을 파악해 보자.


(1) CGameEdu01 class에서 삼각형 렌더링


① CGameEdu01.h

#pragma once

#include "d3dapp.h"

 

struct CUSTOMVERTEX

{

    FLOAT x, y, z, rhw; // The transformed position for the vertex.

    DWORD color;        // The vertex color.

};

 

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)

 

class CGameEdu01 : public CD3DApp

{

private:

      LPDIRECT3DVERTEXBUFFER9 m_pVB;

 

private:

      virtual void OnInit();

      virtual void OnRender();

      virtual void OnUpdate();

      virtual void OnRelease();

 

public:

      CGameEdu01(void);

      ~CGameEdu01(void);

};

 


② CGameEdu01.cpp

void CGameEdu01::OnInit()

{

     CUSTOMVERTEX vertices[] =

     {

         { 150.0f,  50.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color

         { 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00, },

         {  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff, },

     };

 

     m_pd3dDevice->CreateVertexBuffer( 3*sizeof(CUSTOMVERTEX), 0 ,

                                          D3DFVF_CUSTOMVERTEX,

                                          D3DPOOL_DEFAULT, &m_pVB, NULL );   

 

     VOID* pVertices;

     m_pVB->Lock( 0, sizeof(vertices), (void**)&pVertices, 0 );

     memcpy( pVertices, vertices, sizeof(vertices) );

     m_pVB->Unlock();

}

 

void CGameEdu01::OnRender()

{

     m_pd3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(CUSTOMVERTEX) );

     m_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );

     m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );

}

 

void CGameEdu01::OnRelease()

{

     if( m_pVB != NULL )       

        m_pVB->Release();

}


[실행 결과]


(2) CTriangle class를 제작하여 CGameEdu01에서 렌더링


① CTriangle class의 헤더

#pragma once

#include <d3d9.h>

 

struct CUSTOMVERTEX

{

    FLOAT x, y, z, rhw; // The transformed position for the vertex.

    DWORD color;        // The vertex color.

};

 

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE)

 

class CTriangle

{

private:

      LPDIRECT3DVERTEXBUFFER9 m_pVB;

      LPDIRECT3DDEVICE9  m_pd3dDevice;

 

public:

      void OnInit( LPDIRECT3DDEVICE9  pd3dDevice );

      void OnRender();

      void OnRelease();

 

public:

     CTriangle(void);

     ~CTriangle(void);

};


② CTriangle class의 소스

#include "StdAfx.h"

#include "Triangle.h"

 

 

CTriangle::CTriangle(void)

{

}

 

CTriangle::~CTriangle(void)

{

}

 

void CTriangle::OnInit( LPDIRECT3DDEVICE9  pd3dDevice )

{

     m_pd3dDevice = pd3dDevice;

 

     CUSTOMVERTEX vertices[] =

     {

{ 150.0f,  50.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color

{ 250.0f, 250.0f, 0.5f, 1.0f, 0xff00ff00, },

{  50.0f, 250.0f, 0.5f, 1.0f, 0xff00ffff, },

};

 

m_pd3dDevice->CreateVertexBuffer( 3*sizeof(CUSTOMVERTEX), 0 , D3DFVF_CUSTOMVERTEX,

D3DPOOL_DEFAULT, &m_pVB, NULL );   

 

VOID* pVertices;

m_pVB->Lock( 0, sizeof(vertices), (void**)&pVertices, 0 );

memcpy( pVertices, vertices, sizeof(vertices) );

m_pVB->Unlock();

}

 

void CTriangle::OnRender()

{

m_pd3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(CUSTOMVERTEX) );

m_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );

m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 1 );

}

 

void CTriangle::OnRelease()

{

     if( m_pVB != NULL )       

         m_pVB->Release();

}


③ CGameEdu01의 헤더

#pragma once

#include "d3dapp.h"

#include "Triangle.h"

 

class CGameEdu01 : public CD3DApp

{

private:

      CTriangle m_Triangle;

 

private:

virtual void OnInit();

virtual void OnRender();

virtual void OnUpdate();

virtual void OnRelease();

 

public:

CGameEdu01(void);

~CGameEdu01(void);

};


④ CGameEdu01 class의 소스

#include "StdAfx.h"

#include "GameEdu01.h"

 

 

CGameEdu01::CGameEdu01(void)

{

}

 

CGameEdu01::~CGameEdu01(void)

{

}

 

void CGameEdu01::OnInit()

{

     m_Triangle.OnInit( m_pd3dDevice );

}

 

void CGameEdu01::OnRender()

{

     m_Triangle.OnRender();

}

 

void CGameEdu01::OnUpdate()

{

}

 

void CGameEdu01::OnRelease()

{

     m_Triangle.OnRelease();

}

 


다음검색
현재 게시글 추가 기능 열기

댓글

댓글 리스트
  • 작성자c+++ | 작성시간 20.08.25 똑같이 따라한거 같은데 삼각형 출력이 안되는데 이유가 뭐가 있을까요? 비쥬얼 2015버전으로 하고있는데 그래서 그럴까요? 윈도우 창만 나오고 파란 창만 출력되네요
댓글 전체보기
맨위로

카페 검색

카페 검색어 입력폼