프로그래밍을 위한 블로그
by 릭디아스
카테고리
rss

skin by 꾸자네
DIRECTX10 : 메쉬(MESH)로 그리기2

텍스쳐를 출력하는 가장기본적인 fx문의 전문이다. 텍스쳐자원을 전달하기위한 변수인 texDiffuse가 추가되었다.

matrix matProjection;
matrix matView;
matrix matWorld;

Texture2D texDiffuse;

RasterizerState Culling {
 FillMode = SOLID;
 CullMode = BACK;
 FrontCounterClockwise = false;
};

SamplerState samLinear {
 Filter = MIN_MAG_MIP_LINEAR;
 AddressU = Wrap;
 AddressV = Wrap;
};

struct VS_INPUT {
 float3 pos : POSITION;
 float3 normal : NORMAL;
 float2 tex : TEXCOORD0;
};

struct PS_INPUT {
 float4 pos : SV_POSITION;
 float3 normal : NORMAL;          
 float2 tex : TEXCOORD0;
};

PS_INPUT VS( VS_INPUT input )
{
 PS_INPUT output = (PS_INPUT)0;
 output.pos = mul( float4(input.pos,1), matWorld );
 output.pos = mul( output.pos, mul( matView, matProjection ) );
 output.normal = mul( input.normal, (float3x3)matWorld );
 output.tex = input.tex;
 return output;
}

float4 PS( PS_INPUT input ) : SV_Target
{
 float4 cDiffuse = texDiffuse.Sample( samLinear, input.tex );
 return cDiffuse;

}

technique10 Render
{
 pass P0
 {
  SetVertexShader( CompileShader( vs_4_0, VS() ) );
  SetGeometryShader( NULL );
  SetPixelShader( CompileShader( ps_4_0, PS() ) );

  SetRasterizerState( Culling );
  }
}

보라색 부분이 주요 변경점이다. 이전에 컬러값만을 리턴하던 구조에서 텍스쳐값을 리턴하도록 구조가 바뀌었다.
그외에도 변경점이 있지만 자세한 설명은 관련글에서 다루도록하겠다. 응용프로그램에서 default.fx를 읽도록 했으므로 본코드를 default.fx에 써주면 2면씩 다른 텍스쳐가 입혀진 메스 육면체가 빙글빙글 도는것을 확인할 수 있다.

by 릭디아스 | 2009/04/07 22:00 | 게임 프로그래밍 | 트랙백 | 핑백(1) | 덧글(0)
DIRECTX10 : 메쉬(MESH)로 그리기1

다이렉트 초기화 2 글의 init() 부분의 레이아웃 생성 전까지는 공통코드이니 미리 준비해놓자.
이번예제에서는 삼각형 인덱스 리스트 방법대신 mesh 기법으로 그리기로 한다. 또한 폴리곤에 색을 입히는 대신 텍스쳐를
입혀보도록 하겠다. 

//메쉬관련
ID3DX10Mesh *mesh = NULL;
const DWORD numSubsets = 3; //메시의 서브셋의 수.
// 재질의 정의.
struct Material10 {
 D3DXVECTOR4 diffuse;
 D3DXVECTOR4 ambient;
 D3DXVECTOR4 emissive;
 D3DXVECTOR4 specular;
 float sharpness;
 ID3D10ShaderResourceView* tex;
};
// 메시의 재질
Material10* materials = NULL;
// 텍스처에 대한 뷰.
ID3D10EffectShaderResourceVariable* texDiffuseVariable = NULL;

메쉬에서 사용하는 전역변수를 추가해놓도록하자. 폴리곤을 구성하는 삼각형들은 텍스쳐라던가 질감 색깔등 모두 각각의 성질을 지닐수있다. 하지만 이런 모든 삼각형에대하여 설정을 해줄수는 없는 노릇이다. 그래서 일정단위로 성질을 지정하기위해 삼각형들의 집합을 정의해놓은 것이 서브셋 이라는것이다. 즉 정점3개가 모여서 인덱스(삼각형)를 이루고 인덱스가 모여서 서브셋을 이루게 된다. 본예제에서는 3개의 서브셋을 두어 각 서브셋에 두면씩 할당하도록한다. 즉 3가지의 다른 성질을 각각의 서브셋에 적용할수 있는 것이다.

텍스쳐를 입히기위해서는 정점의 레이아웃부터 재정의 해주어야한다. 기존의 레이아웃은 포지션과 컬러값을 가졌지만 텍스쳐를 입히기 위해서는 컬러값대신 노말값과 텍스쳐좌표값을 가져야한다.


 D3D10_INPUT_ELEMENT_DESC elements[] = {
  { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
  { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 },
  { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D10_INPUT_PER_VERTEX_DATA, 0 },
 };
 //레이아웃 생성 및 지정
 ID3D10InputLayout *inputLayout;
 UINT numElements = sizeof(elements) / sizeof(elements[0]);
 D3D10_PASS_DESC passDesc;
 technique->GetPassByIndex(0)->GetDesc(&passDesc);
 device->CreateInputLayout(elements, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &inputLayout);
 device->IASetInputLayout(inputLayout);
    
이전 예제와 비교해서 elements 부분만 달라지고 생성및 지정은 동일하다.
이제는 정점 버퍼와 인덱스 버퍼를 채울차례인데 이전에 사용햇던 MyVertex 구조체는 포지션과 컬러값을 가지므로 새로정의한
레이아웃과 맞지않는다. 대신에 새로운 구조체인 SimpleVertex를 사용하도록 하자.


struct SimpleVertex
{
 D3DXVECTOR3 pos;
 D3DXVECTOR3 normal;
 D3DXVECTOR2 tex;
};

SimpleVertex에 기반하여 버퍼내용을 채워주도록한다. 다른점은 삼각형 인덱스 리스트 방법은 중복없는 정점을 나열하고 그것의 인덱스를 참조하여 그리지만 mesh는 중복을 허용하는 정점을 표기해야한다. 즉 한면당 4개의 꼭지점씩 총 24개의 정점을 나열한다.

 
SimpleVertex vertices[] = { // 박스를 생성함.
  // 전면.
  { D3DXVECTOR3(-1.0f, -1.0f, -1.0f ), D3DXVECTOR3( 0.0f,  0.0f, -1.0f ), D3DXVECTOR2(0.0f, 0.0f) },
  { D3DXVECTOR3(-1.0f,  1.0f, -1.0f ), D3DXVECTOR3( 0.0f,  0.0f, -1.0f ), D3DXVECTOR2(0.0f, 1.0f) },
  { D3DXVECTOR3( 1.0f,  1.0f, -1.0f ), D3DXVECTOR3( 0.0f,  0.0f, -1.0f ), D3DXVECTOR2(1.0f, 1.0f) },
  { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR3( 0.0f,  0.0f, -1.0f ), D3DXVECTOR2(1.0f, 0.0f) },
  // 후면.
  { D3DXVECTOR3(-1.0f, -1.0f,  1.0f ), D3DXVECTOR3( 0.0f,  0.0f,  1.0f ), D3DXVECTOR2(0.0f, 0.0f) },
  { D3DXVECTOR3( 1.0f, -1.0f,  1.0f ), D3DXVECTOR3( 0.0f,  0.0f,  1.0f ), D3DXVECTOR2(0.0f, 1.0f) },
  { D3DXVECTOR3( 1.0f,  1.0f,  1.0f ), D3DXVECTOR3( 0.0f,  0.0f,  1.0f ), D3DXVECTOR2(1.0f, 1.0f) },
  { D3DXVECTOR3(-1.0f,  1.0f,  1.0f ), D3DXVECTOR3( 0.0f,  0.0f,  1.0f ), D3DXVECTOR2(1.0f, 0.0f) },
  // 윗면.
  { D3DXVECTOR3(-1.0f,  1.0f, -1.0f ), D3DXVECTOR3( 0.0f,  1.0f,  0.0f ), D3DXVECTOR2(0.0f, 0.0f) },
  { D3DXVECTOR3(-1.0f,  1.0f,  1.0f ), D3DXVECTOR3( 0.0f,  1.0f,  0.0f ), D3DXVECTOR2(0.0f, 1.0f) },
  { D3DXVECTOR3( 1.0f,  1.0f,  1.0f ), D3DXVECTOR3( 0.0f,  1.0f,  0.0f ), D3DXVECTOR2(1.0f, 1.0f) },
  { D3DXVECTOR3( 1.0f,  1.0f, -1.0f ), D3DXVECTOR3( 0.0f,  1.0f,  0.0f ), D3DXVECTOR2(1.0f, 0.0f) },
  // 아래면.
  { D3DXVECTOR3(-1.0f, -1.0f, -1.0f ), D3DXVECTOR3( 0.0f, -1.0f,  0.0f ), D3DXVECTOR2(0.0f, 0.0f) },
  { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR3( 0.0f, -1.0f,  0.0f ), D3DXVECTOR2(0.0f, 1.0f) },
  { D3DXVECTOR3( 1.0f, -1.0f,  1.0f ), D3DXVECTOR3( 0.0f, -1.0f,  0.0f ), D3DXVECTOR2(1.0f, 1.0f) },
  { D3DXVECTOR3(-1.0f, -1.0f,  1.0f ), D3DXVECTOR3( 0.0f, -1.0f,  0.0f ), D3DXVECTOR2(1.0f, 0.0f) },
  // 왼쪽면.
  { D3DXVECTOR3(-1.0f, -1.0f,  1.0f ), D3DXVECTOR3(-1.0f,  0.0f,  0.0f ), D3DXVECTOR2(0.0f, 0.0f) },
  { D3DXVECTOR3(-1.0f,  1.0f,  1.0f ), D3DXVECTOR3(-1.0f,  0.0f,  0.0f ), D3DXVECTOR2(0.0f, 1.0f) },
  { D3DXVECTOR3(-1.0f,  1.0f, -1.0f ), D3DXVECTOR3(-1.0f,  0.0f,  0.0f ), D3DXVECTOR2(1.0f, 1.0f) },
  { D3DXVECTOR3(-1.0f, -1.0f, -1.0f ), D3DXVECTOR3(-1.0f,  0.0f,  0.0f ), D3DXVECTOR2(1.0f, 0.0f) },
  // 오른쪽면.
  { D3DXVECTOR3( 1.0f, -1.0f, -1.0f ), D3DXVECTOR3( 1.0f,  0.0f,  0.0f ), D3DXVECTOR2(0.0f, 0.0f) },
  { D3DXVECTOR3( 1.0f,  1.0f, -1.0f ), D3DXVECTOR3( 1.0f,  0.0f,  0.0f ), D3DXVECTOR2(0.0f, 1.0f) },
  { D3DXVECTOR3( 1.0f,  1.0f,  1.0f ), D3DXVECTOR3( 1.0f,  0.0f,  0.0f ), D3DXVECTOR2(1.0f, 1.0f) },
  { D3DXVECTOR3( 1.0f, -1.0f,  1.0f ), D3DXVECTOR3( 1.0f,  0.0f,  0.0f ), D3DXVECTOR2(1.0f, 0.0f) },
 };

 WORD indices[] = {
  0, 1, 2,  0, 2, 3, // 앞면.
  4, 5, 6,  4, 6, 7, // 뒷면.
  8, 9, 10,  8, 10, 11, // 윗면.
  12, 13, 14,  12, 14, 15, // 아랫면.
  16, 17, 18,  16, 18, 19, // 왼쪽면.
  20, 21, 22,  20, 22, 23, // 오른쪽면.
 };

정점의 첫번째 인자는 포지션값이고 두번쨰 인자는 그려질 면의 노말이다. 노말이 0.0f 0.0f -1.0f 라면 xyz에 기준하여 z축의 앞쪽에 그리게된다. 0.0f 0.0f 1.0f라면 뒷면에 그리는 것이된다. 세번쨰 인자값은 텍스쳐좌표로 0과 1의 사이값을 가지며 시작위치와 끝위치를 가르킨다. 직접 수치를 바꿔보면 더 쉽게 알수있을 것이다.

//메시생성
 D3DX10CreateMesh(device, elements, numElements, "POSITION", 24, 12, 0, &mesh);

메시를 생성한다 24는 생성할 정점의 개수이며 12는 삼각형의 개수이다.

 //메쉬의 정점버퍼채우기
 ID3DX10MeshBuffer *vb = NULL;
 mesh->GetVertexBuffer(0, &vb);
 SIZE_T stRawVertices = 0;
 unsigned __int8 *pRawVertices = NULL;
 vb->Map(reinterpret_cast<void**>(&pRawVertices), &stRawVertices);
 memcpy_s(pRawVertices, stRawVertices, (const void*)&vertices[0], sizeof(SimpleVertex) * 24); //정점의 갯수만큼
 vb->Unmap();

 //메쉬의 인덱스버퍼채우기
 ID3DX10MeshBuffer *ib = NULL;
 mesh->GetIndexBuffer(&ib);
 SIZE_T stRawIndices = 0;
 unsigned __int16 *pRawIndices = NULL;
 ib->Map(reinterpret_cast<void**>(&pRawIndices), &stRawIndices);
 memcpy_s(pRawIndices, stRawIndices, (const void*) &indices[0], sizeof(unsigned __int16) *36); //인덱스의 갯수만큼
 ib->Unmap();

이부분은 범용적인부분이니 그대로 표기하고 넘어간다.

//메쉬속성표
 //삼각형의 단위대로 지정하되 현재는 4개의 삼각형(2면)을 하나의 서브셋으로 둔다
 D3DX10_ATTRIBUTE_RANGE *attrTable = new D3DX10_ATTRIBUTE_RANGE[numSubsets];
 for(DWORD i = 0; i < numSubsets; i++)
 {
  attrTable[i].AttribId = i; //속성아이디 세팅할 서브셋의 번호
  attrTable[i].FaceStart = i*4; //서브셋에 속한 삼각형 인덱스의 오프셋(시작점)
  attrTable[i].FaceCount = 4;  //서브셋 내의 삼각형 갯수
  attrTable[i].VertexStart = i*8; //현재서브셋과 연결된 정점의 오프셋 (시작점)
  attrTable[i].VertexCount = 8; //현재서브셋내의 정점의 갯수
 }
 mesh->SetAttributeTable(attrTable, static_cast<UINT>(numSubsets));
 delete attrTable;
 mesh->GenerateAttributeBufferFromTable();
 //메쉬의 인접정보들을 생성한다
 mesh->GenerateAdjacencyAndPointReps(0.0f);

메쉬의 중요한 속성인 서브셋에 대한 정의를 한다. 메쉬로 작성이 어려운점하나가 메쉬속성표에 있는 정점과 인덱스의 순서가 유기적으로 결합되기때문에 순서를 잘고려해야한다는 점이다. 그것을위해 정점의 중복을 허용하고 직관적으로 버퍼를 채워서 반복함수로 계산하귀 쉽도록 구성하였다.

 //메쉬의 최적화
 mesh->Optimize( D3DX10_MESHOPT_COMPACT | D3DX10_MESHOPT_ATTR_SORT | D3DX10_MESHOPT_VERTEX_CACHE, NULL, NULL);
 mesh->CommitToDevice();//메쉬제출

메쉬최적화는 렌더링을 효율적으로 하기위한 함수로 다양한 옵션이있다. 범용적인 부분이므로 본예제와 같이 사용해도 무관하다.
메쉬데이터를 전부 작성한후에 메쉬제출 꼭해주어야한다. 이것으로 메쉬에 대한 초기화는 일단 마쳤다.

 char* textureFilenames[numSubsets] = { "brick0.jpg", "brick1.jpg", "brick2.jpg" };
 materials = new Material10[numSubsets];
 for (int i=0; i< numSubsets; i++) {
  materials[i].ambient=D3DXVECTOR4(1,1,1,1); //RGBA
  materials[i].diffuse=D3DXVECTOR4(1,1,1,1); //RGBA
  materials[i].specular=D3DXVECTOR4(1,1,1,1); //RGBA
  materials[i].emissive=D3DXVECTOR4(1,1,1,1); //RGBA
  materials[i].sharpness=10.0f;
  // 텍스처를 로드한다.
  D3DX10CreateShaderResourceViewFromFile(device, textureFilenames[i], NULL, NULL, &materials[i].tex, NULL);
  texDiffuseVariable = effect->GetVariableByName( "texDiffuse" )->AsShaderResource(); //for mesh


텍스쳐맵핑에 관한 자세한 설명은 텍스쳐에 관한 글에서 하도록하겠다. 여기서는 서브셋의 갯수만큼 파일을읽어서 마테리얼이란 구조체에 담는다고 생각하자. 물론 마테리얼의 갯수는 서브셋의 갯수와 같은 3개이다.

bool render(float timeDelta)
{
 //응용에 따른 그리기 (t값은 시간값으로 변화한다. )
 static float t = 0.0f;
 t += timeDelta * 1.0f;
 if( t >= D3DX_PI * 2.0f) t = 0.0f;
 D3DXMatrixRotationY(&matWorld, t);
 matWorldVariable->SetMatrix( (float*)&matWorld);


 device->ClearRenderTargetView(renderTargetView, D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f)); //버퍼초기화
 device->ClearDepthStencilView(depthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0); //깊이버퍼 초기화


 D3D10_TECHNIQUE_DESC techDesc;
 technique->GetDesc(&techDesc);
 for(UINT p = 0; p < techDesc.Passes; p++) {
  for(int i = 0; i < 3; i++) {
   if(texDiffuseVariable && materials[i].tex) {
    texDiffuseVariable->SetResource( materials[i].tex);

   }
  technique->GetPassByIndex(p)->Apply(NULL);
  mesh->DrawSubset(i);
  }
 }


 swapChain->Present(0, 0);
 return true;
}

이전예제와 동일하게 timeDelta값으로 오브젝트를 회전하도록 위치행렬을 변경해준후 메쉬에 텍스쳐자원을 제공해서 그려주었다.
이전예제와 다른부분은 보라색 부분밖에 없다. 다만 서브셋의 갯수만큼 그려주기때문에 for문을 이용해서 각각의 서브셋을 그려주었다.

이전예제에서 사용한 fx파일은 픽셀쉐이더 부분이 컬러값을 출력하도록 되어있기때문에 텍스쳐를 입히도록 변경해주어야한다.
다음글에서 fx파일을 다루도록 한다.

brick0.jpgbrick1.jpgbrick2.jpg
by 릭디아스 | 2009/04/07 21:45 | 게임 프로그래밍 | 트랙백 | 핑백(1) | 덧글(0)
DIRECTX10 : 초기화 과정3

init() 함수의 첫줄에 있는

D3DX10CreateEffectFromFile( TEXT("default.fx"), NULL, NULL, "fx_4_0", D3D10_SHADER_ENABLE_STRICTNESS, 0, device, NULL, NULL, &effect, NULL, NULL );

문을보면 default.fx라는 파일을 읽도록 되어있다. 아래내용을 가진 default.fx를 추가하도록하자.
이하의 내용은 c언어와 유사하지만 HLSL언어로 정의 되어있으며 자세한 내용은 생략한다.


matrix matWorld;
matrix matView;
matrix matProjection;

struct VS_Output
{
 float4 pos : SV_POSITION;
 float4 color : COLOR0;
};

VS_Output VS( float4 pos : POSITION, float4 color : COLOR )
{
 VS_Output output = (VS_Output)0;
 output.pos = mul( mul( mul(pos, matWorld), matView), matProjection);
 output.color = color;
 return output;
}

float4 PS( VS_Output input ) : SV_Target
{
  return input.color;
}

technique10 Render
{
 pass P0
 {
   SetVertexShader( CompileShader( vs_4_0, VS() ) );
   SetGeometryShader( NULL );
   SetPixelShader( CompileShader( ps_4_0, PS() ) );
  }
}

앞서서 init문에 fx문의 시작점을 Render로 정해주었다. 즉 Rander부분의 passP0부분을 매인함수처럼 생각하고 읽는다.
크게 3개의 함수가 정의되어있는데

SetVertexShader 는 앞서정한 matWorld, matView, matProject의 정보를 가지고 각종 변환을 행한다.(주로 크기와 위치정보)
SetPixelShader 는 버텍스쉐이더에서 출력한 벡터정보를 토대로 색이나 텍스쳐를 입히고 질감이나 각종 특수효과를 수행한다.
이예제에서는 텍스쳐를 입히지않고 칼라정보를 그대로 리턴시켜서 정점구조체에서 입력한 칼라값만 표현하도록하였다.

이제 다이렉트를 이용하는 최소한의 과정이 모두끈났다. 실행하면 회전하는 정육면체를 볼수있다.

by 릭디아스 | 2009/04/07 16:29 | 게임 프로그래밍 | 트랙백 | 핑백(1) | 덧글(0)
DIRECTX10 : 초기화 과정2

앞서 DX_INIT 부분에서 기초초기화를 마쳤으니 이제 DX_MAIN을 만들자. 굉장히 수정이 빈번한 파일이므로 헤더를 따로두어 코드를 정리하는 것은 나중일로 미루고 모든것을 DX_MAIN에 넣도록 하자.

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;

ID3D10Device *device = NULL;
IDXGISwapChain *swapChain = NULL;
ID3D10RenderTargetView *renderTargetView = NULL;
ID3D10DepthStencilView *depthStencilView = NULL;
ID3D10Texture2D *depthStencil = NULL;
ID3D10Texture2D* tex = NULL;
ID3D10Effect * effect = NULL;
ID3D10EffectTechnique * technique = NULL;
//변환행렬
D3DXMATRIX matWorld;
D3DXMATRIX matView;
D3DXMATRIX matProjection;
// 변환행렬에 대한 상수버퍼 변수들.
ID3D10EffectMatrixVariable* matWorldVariable = NULL;
ID3D10EffectMatrixVariable* matViewVariable = NULL;
ID3D10EffectMatrixVariable* matProjectionVariable = NULL;

꽤많은 양의 전역변수지만 이것이 전부는아니다. 당장 알아둬야할 것들만 선언해두었다.
맨위의 스크린 높이와 너비값은 문자상수로 정해두었다. 이렇게하지않으면 크기가 변할떄 소스를 이곳저곳 고치게 된다.
디바이스, 스왑체인, 렌더타겟뷰, 깊이스텐실, 깊이스텐실뷰는 앞서 DX_INIT에 있는 함수에 인자로 주었던 변수들을 이곳에 선언해둔것이다. 그아래 두개의 ID3D10Texture2D 변수는 텍스쳐를 입히기위해 필요한 변수이다. 텍스쳐란 메쉬구체(폴리곤)에 입히는 그림을 말한다. 
 그아래의 이펙트 변수와 이펙트테크니크 변수는 fx파일과의 소통을 위한 변수이다. fx파일은 cpp파일과는 별도로 존제하며 그래픽카드의 GPU에서 처리하는 고유의 처리문을 모아놓은 것이다. fx파일에 대해서는 차후 다른 글로설명하도록 하겠다.

행렬 변수 D3DXMATRIX 는 4X4의 행렬로 이루어진 구조체인데 우리가 직접 행렬값을 조작하는 것은아니고 제공되는 편의함수를 이용하여 값을 변화시키게 된다. matWorld는 전체 공간에 대한 하나의 오브젝트의 위치값(물체의 크기 방향또한 포함한다)을 표현하며 matView는 전체공간을 우리가 바라보는 시점이 된다. 흔히 1인칭, 3인칭시점을 이루어 말하는것은 matView가 바라보는 위치에 기준하여 정해지게 되는 것이다. matProjecton은 투사라 말하며 matView가 이동하며 전체공간을 바라보는것은 사실 matView의 시점에 기준하여 전체공간이 재정렬 된다는 것을 의미한다. 이것에 대한 표현을 관장한다. 투사는 어려운부분이니 잘모르면 일단 넘어가도록 한다.

// 텍스처에 대한 뷰.
ID3D10EffectShaderResourceVariable* texDiffuseVariable = NULL;
ID3D10ShaderResourceView* textureViewExternal = NULL; // 외부에서 로드한 텍스처.
// 재질 칼라.
D3DXVECTOR4 diffuseColor;
// 재질 칼라에 대한 상수버퍼 변수들.
ID3D10EffectVectorVariable* diffuseColorVariable = NULL;

이것또한 자주이용하는 전역변수이니 일단 추가해놓자. 설명은 추후 해당부분에서 하도록하겠다.

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
 switch(msg) {
  case WM_DESTROY:
   ::PostQuitMessage(0);
   break;
 }
 return ::DefWindowProc(hWnd, msg, wParam, lParam);
}

제일먼저 콜백함수인 WndProc을 만든다. 매우 심플하며 차후 필요할때 기능을 추가해서 사용하면 된다.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
 HWND hWnd = d3d::initWin(hInstance, nShowCmd, SCREEN_WIDTH, SCREEN_HEIGHT);

 d3d::initD3D(hWnd, SCREEN_WIDTH, SCREEN_HEIGHT, &device, &swapChain, &renderTargetView, &depthStencil, &depthStencilView);
 init(); 
 d3d::doMessageLoop(render);
 release();
 d3d::releaseWin(hInstance);
 d3d::releaseD3D(&device, &swapChain, &renderTargetView, &depthStencil, &depthStencilView);
 return 0;
}

핸들값을 얻음과 동시에 창을 생성한다. 그후 핸들값, 높이, 너비를 포함한 각종 변수를 인자로 주어 기초 초기화를 한다.
init()는 자주변화하는 초기화 코드를 이곳에 넣으며 그뒤에 모든 자원을 해제시킨후 종료하는 심플한 구성을 지닌다.

void init()

render함수에 그리기준비를 모두 마치기 위한 초기화 함수로 내용이 방대하므로 하나씩 살펴보기로 한다.

 D3DX10CreateEffectFromFile( TEXT("default.fx"), NULL, NULL, "fx_4_0", 
                D3D10_SHADER_ENABLE_STRICTNESS, 0, device, NULL, NULL, &effect, NULL, NULL );
 technique = effect->GetTechniqueByName("Render");

fx파일은 그래픽카드내에서 그림을 표현할 방식(재질, 색, 효과)을 구현하는 코드의 모음 이라고 생각하자. fx파일을 읽어들이고
그중 Render라는 패스명을 선택한다. Render라는 함수명 호출이라생각해도 좋다. Main함수를 부르듯이 fx파일상의 Main함수를 지정한다.

D3DXMatrixIdentity(&matWorld); //월드행렬의 초기화

기본 월드행렬을 정의한다. 이후에 오브젝트들을 월드상에 다른곳에 배치하려면 D3DXMatrixTranslation(&matWorld, 0.0f, 0.0f, 0.0f); 와같은 각종편의함수를 이용하여 월드행렬을 매번 조정해주어야한다.

 //뷰행렬의 초기화
 D3DXVECTOR3 Eye( 0.0f, 2.0f, -20.0f);
 //뷰의 위치 인자1: - 왼쪽이동 + 우측이동  인자2: + 위로이동 - 아래로이동 인자3 : + 앞으로이동 - 뒤로이동
 D3DXVECTOR3 At(0.0f, 0.0f, 0.0f);
 D3DXVECTOR3 Up(0.0f, 1.0f, 0.0f);
D3DXMatrixLookAtLH(&matView, &Eye, &At, &Up);

뷰행렬은 월드행렬을 바라보는 위치와 그 방향을 정의한다. At은  월드행렬의 x,y,z좌표상의 위치 Up은 위와 아래, Eye는 바라보는 현재 위치에서 바라보는 방향을 정의한다. 그후 각각정의한 3개의 벡터값을 matView에 적용한다.

//투사행렬의 초기화
 D3DXMatrixPerspectiveFovLH(&matProjection, (float)D3DX_PI * 0.25f, (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT, 0.1f, 1000.0f);


투사행렬을 정의한다.(float)D3DX_PI * 0.25f 에서 0.25를 더 큰값으로 변화시킬수록 시야각이 변한다. 끝의 두개의 인자는 투사도를 나타내는것으로 값을 작게할수록 한번에 보이는 시야거리가 제한된다.

 //상수버퍼 변수들에 값을 지정한다
 matWorldVariable = effect->GetVariableByName("matWorld")->AsMatrix();
 matViewVariable = effect->GetVariableByName("matView")->AsMatrix();
 matProjectionVariable = effect->GetVariableByName("matProjection")->AsMatrix();
 matWorldVariable->SetMatrix( (float*)&matWorld);
 matViewVariable->SetMatrix( (float*)&matView);
 matProjectionVariable->SetMatrix( (float*)&matProjection);


응용프로그램상(DX_MAIN)에서 정의한 행렬변수를 fx파일내의 동일한 이름의 행렬변수로 값을 복사한다. 즉 initD3D에서 정의한
변수내용을 fx파일내로 연동한다. 

여기까지가 공통부분 초기화이다. 이하부터는 삼각형 인덱스 리스트 그리기와 메쉬로 그리기로 할때 각각 다른 코드를 이용한다.
여기에서는 삼각형 인덱스 리스트 그리기로 하도록 한다.

 //정점의 입력 레이아웃 정의
 D3D10_INPUT_ELEMENT_DESC elements[] = {
  { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
  { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 }
 };
 //레이아웃 생성 및 지정
 ID3D10InputLayout *inputLayout;
 UINT numElements = sizeof(elements) / sizeof(elements[0]);
 D3D10_PASS_DESC passDesc;
 technique->GetPassByIndex(0)->GetDesc(&passDesc);
 device->CreateInputLayout(elements, numElements, passDesc.pIAInputSignature, passDesc.IAInputSignatureSize, &inputLayout);
 device->IASetInputLayout(inputLayout);


하나의 폴리곤은 하나의 삼각형으로 그려지며 하나의 삼각형은 3개의 정점(꼭지점)을 가진다. 정점의 레이아웃이란 정점의 속성을 정의하는 것이다. 정점구조체 elements를 살펴보면 이예제의 정점은 포지션 정보와 컬러 정보를 가짐을 알수가있다.
레이아웃 생성및 지정은 거의 변하지 않으므로 본문그대로 표기하도록 한다.

 MyVertex vertices[] = {
  { D3DXVECTOR3( -2.0f,  2.0f, -2.0f ), D3DXVECTOR4( 0.0f, 0.0f, 1.0f, 1.0f ) },
  { D3DXVECTOR3(  2.0f,  2.0f, -2.0f ), D3DXVECTOR4( 0.0f, 1.0f, 0.0f, 1.0f ) },
  { D3DXVECTOR3(  2.0f,  2.0f,  2.0f ), D3DXVECTOR4( 0.0f, 1.0f, 1.0f, 1.0f ) },
  { D3DXVECTOR3( -2.0f,  2.0f,  2.0f ), D3DXVECTOR4( 1.0f, 0.0f, 0.0f, 1.0f ) },
  { D3DXVECTOR3( -2.0f, -2.0f, -2.0f ), D3DXVECTOR4( 1.0f, 0.0f, 1.0f, 1.0f ) },
  { D3DXVECTOR3(  2.0f, -2.0f, -2.0f ), D3DXVECTOR4( 1.0f, 1.0f, 0.0f, 1.0f ) },
  { D3DXVECTOR3(  2.0f, -2.0f,  2.0f ), D3DXVECTOR4( 1.0f, 1.0f, 1.0f, 1.0f ) },
  { D3DXVECTOR3( -2.0f, -2.0f,  2.0f ), D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f ) },
 };
 D3D10_BUFFER_DESC bd;
 bd.Usage = D3D10_USAGE_DEFAULT;
 bd.ByteWidth = sizeof(MyVertex) * 8;  //정점 버퍼의 갯수
 bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
 bd.CPUAccessFlags = 0;
 bd.MiscFlags = 0;
 D3D10_SUBRESOURCE_DATA initData;
 initData.pSysMem = vertices;
 ID3D10Buffer *vertexBuffer;
 device->CreateBuffer(&bd, &initData, &vertexBuffer);


앞서 정의한 정점의 레이아웃대로 정점을 만드는 부분이다. 레이아웃에서 포지션값과 컬러값을 넣어주기로 했으므로 그에 맞춰서

struct MyVertex
{
 D3DXVECTOR3 pos;
 D3DXVECTOR4 color;
};

형태의 구조체를 생성한후 내용을 채워준다. 위 예제는 직육면체의 정점이며 꼭지점이 8개 이므로 8개를 각각 표기한다.
이후에 작성한 vertices 구조체를 토대로 정점 버퍼를 생성한다.

 DWORD indices[] = {
  3,1,0,  2,1,3,
  0,5,4,  1,5,0,
  3,4,7,  0,4,3,
  1,6,5,  2,6,1,
  2,7,6,  3,7,2,
  6,4,5,  7,4,6,
 };

 bd.Usage = D3D10_USAGE_DEFAULT; //정점 버퍼가 기록될 메모리의 종류 결정
 bd.ByteWidth = sizeof(DWORD) *36;  //인덱스 버퍼의 개수만큼 적어준다
 bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
 bd.CPUAccessFlags = 0;
 bd.MiscFlags = 0;
 initData.pSysMem = indices;
 ID3D10Buffer *indexBuffer;
 device->CreateBuffer(&bd, &initData, &indexBuffer);

삼각형인덱스리스트 그리기방법은 인덱스 목록에 적힌 정점의 번호에 따라 삼각형을 그려나가는 방법이다. 각각 3개로 짝을이루는 정점번호가 시계방향으로 그려질때 하나의 삼각형을 표현하게 된다. 정육면체는 12개의 삼각형으로 그려져있으므로 모두 인덱스구조체에 표기하도록 하자. 그후 정점구조체를 인덱스리스트에 맞게 수정하여 인덱스 버퍼를 생성한다.

 //정점버퍼의 지정
 UINT stride = sizeof( MyVertex );
 UINT offset = 0;
 device->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
 //인덱스버퍼의 지정
 device->IASetIndexBuffer(indexBuffer, DXGI_FORMAT_R32_UINT, 0);
 //기본도형위상의 지정
 device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 

실제 그릴 도구로 정점버퍼인 vertexBuffer와 인덱스버퍼 indexBuffer를 디바이스에 적용시켜준다. 만약 모양이 다른 다른 오브젝트를 더그리고싶다면 또다른 정점버퍼와 인덱스버퍼를 생성해서 디바이스에 적용시켜준후 그리면 된다.
기본도형위상은 몇가지 다른것이 있지만 거의 삼각형리스트 방식을 사용한다.

여기까지가 그리기준비 초기화함수 init의 마지막이다. 이외에 그리기전에 준비할사항이 있다면 이곳에 하면된다.

void release()
{
 if(device)
  device->ClearState();


앞서 이용한 자원들을 해제하는 함수이다.

bool render(float timeDelta)
{
 //응용에 따른 그리기 (t값은 시간값으로 변화한다. )
 static float t = 0.0f;
 t += timeDelta * 1.0f;
 if( t >= D3DX_PI * 2.0f) t = 0.0f;

 D3DXMatrixRotationY(&matWorld, t);

matWorldVariable->SetMatrix( (float*)&matWorld);

 device->ClearRenderTargetView(renderTargetView, D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f)); //버퍼초기화
 device->ClearDepthStencilView(depthStencilView, D3D10_CLEAR_DEPTH, 1.0f, 0); //깊이버퍼 초기화

 D3D10_TECHNIQUE_DESC techDesc;
 technique->GetDesc(&techDesc);
 for(UINT p = 0; p < techDesc.Passes; p++) {
  technique->GetPassByIndex(p)->Apply(NULL);
  device->DrawIndexed(36, 0, 0);  //첫인자: 인덱스 버퍼의 갯수
 }

 swapChain->Present(0, 0);
 return true;
}

실제 그림을 그려주는 부분이다. 메세지루프문에서 timeDelta값을 받아왔으므로 이를 이용해서 시간값에 따라 변화하는 t값을 구하고 t값을 이용해서  D3DXMatrixRotationY(&matWorld, t); 문으로 t값의 변화에 따라 Y축으로 월드행렬을 변화시키게된다. 즉 이후에 그려지는 오브젝트를 Y축을 기준으로 회전시키게 된다.
그림을 그리는 모든 사전준비는 버퍼초기화함수 전까지 하면된다. 버퍼초기화 함수이후부터 스왑체인 프리센트문사이에 실제로 드로우문을 사용하면된다.
테크닉 구조체를 생성하여 테크닉정보를 담고 이것을 FOR문으로 패스의수만큼 돌려서 drawindexed문으로 그리면 된다.

이것으로 끝이아니다. 아직 한가지 할일이 남았다. 다음글에서 계속 하도록 한다.

 

by 릭디아스 | 2009/04/07 12:58 | 게임 프로그래밍 | 트랙백 | 핑백(1) | 덧글(0)
DIRECTX10 : 초기화 과정1

선행과정 : 다이렉트SDK를 설치하고 도구->옵션->프로젝트및 솔루션->VC++디렉토리 포함파일에 SDK\include 를 추가하고 라이브러리 파일에 SDK\lib\x86를 추가한다. 프로젝트 속성->링커->입력 추가종속성에 d3d10.lib d3dx10.lib winmm.lib를 추가한다.

메인이 되는 cpp로 DX_MAIN을 두며 거의 변하지 않는 초기화 코드는 DX_INIT에 두기로 하겠다.
DX_MAIN에는 #include "DX_INIT.h" 를 시키며  DX_INIT에도 역시 #include "DX_INIT.h"를 추가한다.
DX_INIT에는 다이렉트를 이용하기위해
#pragma once
#include <d3dx10.h>
를 추가한다.


DX_INIT.h

#pragma once
#include <d3dx10.h>

// 자원 안전 반환 매크로.
#define SAFE_DELETE_ARRAY(p) { if (p) { delete[] (p); (p)=NULL; } }
#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } }
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
#define SAFE_FREE(p) { if (p) { free(p); (p)=NULL; } }

namespace d3d
{
 HWND initWin(HINSTANCE hInstance, int nShowCmd, int width, int height);
 int doMessageLoop(bool (*renderFunction)(float timeDelta));
 bool releaseWin(HINSTANCE hInstance);

 void initD3D(HWND hWnd,int width, int height, ID3D10Device **device, IDXGISwapChain **swapChain, ID3D10RenderTargetView **renderTargetView, ID3D10Texture2D **depthStencil, ID3D10DepthStencilView **depthStencilView);
 void releaseD3D(ID3D10Device **device, IDXGISwapChain **swapChain, ID3D10RenderTargetView **renderTargetView, ID3D10Texture2D **depthStencil, ID3D10DepthStencilView **depthStencilView);
}

하나의 CPP에 모든 초기화를 담기에는 내용이 너무 방대하다. MAIN과 INIT를 따로 분리하기로하고 네임스페이스 d3d로 두어
동일하게 이용하도록 한다.

INIT에는 기본적으로 5개의 함수가 담겨있다. DX_INIT.h에 선언된 함수의 정의는 DX_INIT.cpp에 기록하자.


HWND d3d::initWin(HINSTANCE hInstance, int nShowCmd, int width, int height)
{
 LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

 WNDCLASS wc;
 wc.cbClsExtra = 0;
 wc.cbWndExtra = 0;
 wc.hIcon = ::LoadIcon(hInstance, (LPCTSTR)IDI_APPLICATION);
 wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
 wc.lpfnWndProc = (WNDPROC)WndProc;
 wc.hInstance = hInstance;
 wc.style = CS_HREDRAW | CS_VREDRAW;
 wc.lpszClassName = "init 3d";
 wc.lpszMenuName = 0;
 RegisterClass(&wc);

 RECT screenrect;
 DWORD dwWindowStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
 ::SetRect(&screenrect, 0, 0, width, height);
 ::AdjustWindowRect(&screenrect, dwWindowStyle, false);

 HWND hWndMain = ::CreateWindow("init 3d", "MyVertexMaking", WS_OVERLAPPEDWINDOW,
  CW_USEDEFAULT, CW_USEDEFAULT,
  screenrect.right - screenrect.left, screenrect.bottom - screenrect.top,  
  NULL, NULL, hInstance, NULL);

 ShowWindow(hWndMain, nShowCmd);
 UpdateWindow(hWndMain);
 return hWndMain;
}
새창을 생성하는 함수. 직관적인부분이므로 설명은 생략.


int d3d::doMessageLoop(bool (*renderFunction)(float timeDelta))
{
 MSG msg;
 ::ZeroMemory(&msg, sizeof(MSG));
 static float lastTime = (float)::timeGetTime();

 while(msg.message != WM_QUIT) {
  if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
  }
  else {
   float currTime = (float)timeGetTime();
   float timeDelta = (currTime - lastTime) * 0.001f;

   if(! renderFunction(timeDelta)) {
    return 0;
   }
   lastTime = currTime;
  }
 }
 return (int)(msg.wParam);
}
운영체제로 들어온 메시지를 체크하는 메시지 루프부분. 일반적인 메시지루프와 다른점은 동적인 시간값 timeDelta 를 얻어낼수있도록 되어있다. timeDelta는 화면을 그리는 부분인 render함수에 전달되며 매번 render함수를 호출시킨다.


bool d3d::releaseWin(HINSTANCE hInstance)
{
 ::UnregisterClass("init 3d", hInstance);
 return true;
}
다이렉트와 무관한 WIN32API 자원사용부분을 해제해주는 부분이다. 특이점없음..


void initD3D(HWND hWnd,int width, int height, ID3D10Device **device, IDXGISwapChain **swapChain, ID3D10RenderTargetView **renderTargetView, ID3D10Texture2D **depthStencil, ID3D10DepthStencilView **depthStencilView);
중요한 함수이기때문에 선언부부터 하나씩 보도록 하겠다. 장치초기화와 변화가 거의없는 몇가지부분의 초기화를 행하는 부분으로
파일을 나눠관리하기때문에 불가피하게 넘겨주는 인자가 굉장히 많아졌다. 기본적으로 윈도우 핸들값과 높이, 너비 값을 넘겨주고
나머지는 전부 다이렉트에 관련된 주요 포인터변수를 넘겨주게된다.

//장치설정 (스왑체인 구조체 정의 부터 시작)
 DXGI_SWAP_CHAIN_DESC scd;
 ::ZeroMemory(&scd, sizeof(scd));
 scd.BufferCount = 1;
 scd.BufferDesc.Width = width;
 scd.BufferDesc.Height = height;
 scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
 scd.BufferDesc.RefreshRate.Numerator = 60;
 scd.BufferDesc.RefreshRate.Denominator = 1;
 scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
 scd.OutputWindow = hWnd;
 scd.SampleDesc.Count = 1;
 scd.SampleDesc.Quality = 0;
 scd.Windowed = true;

스왑체인 구조체에서는 그림을 그릴버퍼에 대한 정의를 한다. 버퍼의 높이와 너비는 윈도우와 같도록주고 포맷은 거의 변함이 없다
리프레쉬레이트는 주사율값이다 USAGE값은 거의 변함이 없으며 아웃풋윈도우는 그려줄 윈도우 핸들값을 주면된다.
샘플 카운트와 퀄리티는 1과 0을 주면 무난하다

//장치설정 (장치와 스왑체인의 동시 생성)
 D3D10CreateDeviceAndSwapChain(NULL, D3D10_DRIVER_TYPE_HARDWARE, NULL, 0, D3D10_SDK_VERSION, &scd, swapChain, device);

위에서 생성한 스왑체인 구조체 scd를 토대로 스왑체인과 장치를 동시에 생성한다.

 
//렌더타겟뷰의 생성
 ID3D10Texture2D *backbuffer;
 (*swapChain)->GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&backbuffer);
 (*device)->CreateRenderTargetView(backbuffer, NULL, renderTargetView);
 backbuffer->Release();

스왑체인이 가진 버퍼가 그림을 그린 저장소라고하면 렌더타겟뷰는 그림을 꺼내서 보여주는 문이라고 생각하면 된다.
인자가 이중포인터라 사용시에는 위코드처럼 표기해야한다.


//깊이버퍼(z버퍼링) 생성 *옵션에 따라 이미지에 밀접하게 변화됨*
 D3D10_TEXTURE2D_DESC descDepth;
 descDepth.Width = width;
 descDepth.Height = height;
 descDepth.MipLevels = 0;
 descDepth.ArraySize = 1;
 descDepth.Format = DXGI_FORMAT_D32_FLOAT;
 descDepth.SampleDesc.Count = 1;
 descDepth.SampleDesc.Quality = 0;
 descDepth.Usage = D3D10_USAGE_DEFAULT;
 descDepth.BindFlags = D3D10_BIND_DEPTH_STENCIL;
 descDepth.CPUAccessFlags = 0;
 descDepth.MiscFlags = 0;
 (*device)->CreateTexture2D(&descDepth, NULL, depthStencil);

 D3D10_DEPTH_STENCIL_VIEW_DESC dsvd;
 dsvd.Format = descDepth.Format;
 dsvd.ViewDimension = D3D10_DSV_DIMENSION_TEXTURE2D;
 dsvd.Texture2D.MipSlice = 0;
 (*device)->CreateDepthStencilView((*depthStencil), &dsvd, depthStencilView);

깊이 버퍼 구조체를 생성하고 이를 적용해 깊이스텐실뷰를 생성한다.  스텐실뷰는 on off가 가능하며 off가 가능하다는 이야기는 필수요소는 아니라는 말이된다. 그러나 중요한 부분이므로 나중에 다른 포스트로 다시 다루도록하겠다. 여기서는 소스그대로 이용.

 //렌더타겟뷰와 깊이 스텐실뷰를 지정함
 (*device)->OMSetRenderTargets(1, renderTargetView, (*depthStencilView) ); //z버퍼on시 사용
 //device->OMSetRenderTargets(1, &renderTargetView, NULL); //z버퍼off시 사용

위에서 만든 스텐실버퍼의 적용유무를 결정한다. 여기서는 스텐실버퍼를 적용 하도록한다.

 //뷰포트 생성(클립공간-> 픽셀공간화)
 D3D10_VIEWPORT vp;
 vp.TopLeftX = 0;
 vp.TopLeftY = 0;
 vp.Width = width;
 vp.Height = height;
 vp.MinDepth = 0.0f;
 vp.MaxDepth = 1.0f;
 (*device)->RSSetViewports(1, &vp);

우리는 3D로 그림을 그리지만 우리가 보는 모니터는 실제로 2D의 평면화면이다. 뷰포트에서는 3D의 화면을 2D의 픽셀공간으로
화면을 변환시켜준다. 왼쪽끝이 0,0위치이며 대각선끝으로 윈도우의 사이즈인 길이와 너비값을 주고 높낮이를 0과 1사이로 정의한다.

여기까지가 void d3d::initD3D(~~) 함수의 전문이다.

void d3d::releaseD3D(ID3D10Device **device, IDXGISwapChain **swapChain, ID3D10RenderTargetView **renderTargetView, ID3D10Texture2D **depthStencil, ID3D10DepthStencilView **depthStencilView)
{
 SAFE_RELEASE(*depthStencil);
 SAFE_RELEASE(*depthStencilView);
 SAFE_RELEASE(*renderTargetView);
 SAFE_RELEASE(*swapChain);
 SAFE_RELEASE(*device);
}

함수이름에서 알수있듯이 초기화과정에서 사용했던 자원들을 해제하는 함수이다. 위에서 사용했던 디바이스, 스왑체인, 깊이 스텐실, 깊이스텐실뷰, 렌더타겟뷰 의 객체가 가진 자원을 모두 반환한다.

이것으로 DX_INIT.h 와 DX_INIT.cpp 의 코딩을 마쳤다. 다음 글에서 DX_MAIN.cpp를 보도록한다.

by 릭디아스 | 2009/04/07 12:21 | 게임 프로그래밍 | 트랙백 | 핑백(1) | 덧글(0)
BIND2ND : 단항 조건자의 기능적 조합

replace_if , remove_if 와 같이 단항 연산자를 인자로 요구하는 알고리즘이나 사용자 정의 함수를 인자로 요구하는 알고리즘에서
bind2nd는 유용하게 쓰일수있다. 

bind2nd(FUNC, num) 
ex) bind2nd(multiplies<int>(), 10)

두번째 인자를 가지고 첫번째 인자의 알고리즘을 거친 값을 반환해준다.
값의 비교나 연산등 두가지의 기능을 하나로 합쳐주기때문에 단항연산자의 요구를 충족한다.


#include "stdafx.h"

#include <iostream>
#include <deque>
#include <set>
#include <algorithm>
#include "print.cpp"
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
 set<int, greater<int> > a1;
 deque<int> a2;

 for(int i = 1; i <= 9; ++i)
  a1.insert(i);

 PRINT_ELEMENTS(a1, "inintialized : ");

 //a1의 모든원소들에 10을 곱하여 a2로 이동한다
 transform(a1.begin(), a1.end(),
  back_inserter(a2),
  bind2nd(multiplies<int>(), 10));

 PRINT_ELEMENTS(a2, "transformed : ");

 //70과 동일한 값을 모두 42로 변경한다
 replace_if(a2.begin(), a2.end(), //범위
  bind2nd(equal_to<int>(), 70), //변경 조건
  42);       //새로운 값

 PRINT_ELEMENTS(a2, "replaced : ");

 //50보다 적은 모든 원소를 삭제
 a2.erase(remove_if(a2.begin(), a2.end(), //범위
  bind2nd(less<int>(), 50)),    //제거 조건
  a2.end());

 PRINT_ELEMENTS(a2, "removed : ");

 return 0;
}

이러한 종류의 프로그래밍은 기능적 조합의 결과이다. 이들은 인라인 함수이기때문에 사용자는 좋은 성능을 얻을 수 있다.
이외에도 다른 종류의 함수객체들도 존제한다. 예를들어 어떤 함수객체들은 모든 원소에 대해 멤버 함수를 호출하는 능력을 제공한다.

for_each(a.begin(), a.end(), //범위
             mem_fun_ref(&Person::save)); //동작

mem_fun_ref함수객체는 원소에 대해서 명시된 멤버 함수를 호출한다. 그러므로 a의 모든 원소에 대해서
Person클래스의 save() 맴버함수를 호출한다. 이것은 원소가 person타입이거나 person 의 파생형일때만 유효한다.

by 릭디아스 | 2009/03/30 16:33 | C++ 프로그래밍 | 트랙백 | 덧글(0)
TRANSFORM : 컨테이너값을 조작해서 복사하기

a컨테이너 -- ( transform() )--> b컨테이너 와 같은 역할을 하는 알고리즘이다.
목적지를 소스 자신으로 하면 자신을 조작하는 것도 가능하다.
아래 예제는 transform 으로 제곱을 수행한다.

이 예제부터는 #include "print.cpp"하기로 한다. 이에대한 정보는 본홈페이지의 PRINT_ELEMENTS글을 참고하기 바란다.

#include "stdafx.h"

#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
#include "print.cpp"
using namespace std;

int square(int value)
{
 return value * value;
}

int _tmain(int argc, _TCHAR* argv[])
{
 set<int> a1;
 vector<int> a2;

 for(int i = 1; i <= 9; ++i)
  a1.insert(i);

 PRINT_ELEMENTS(a1, "initialized : ");

 //a1에서 a2로 원소를 이동한다.
 //각원소의 값들은 제곱되어서 이동된다.
 transform(a1.begin(), a1.end(), //소스
  back_inserter(a2),  //목적지
  square);    //동작할 함수

 PRINT_ELEMENTS(a2, "squared : ");
 
 return 0;
}


간단한 조작이라면 우리가 매번 함수를 정의해서 사용할 필요는 없다. STL에는 미리 정의된 여러가지의 함수객체를
제공한다. 여기서는 몇가지 간단한 함수 객체를 소개하고자 한다.

negate<int>()

negate 함수객체는 호출된 모든 int타입의 원소에 대해서 부정된원소(마이너스)를 반환한다. 예 10, 20 -> -10, -20

multiplies<int>()

multiplies 함수객체는 호출된 모든 int타입의 원소에 대해서 자신의 값으로 자신을 곱하게 된다.

bind2nd(FUNC, num)
ex) bind2nd(less<int>, 50)

bind2nd 함수객체는 특별하다. 두번째 인자를 첫번째 인자의 동작으로 결과를 반환한다.
이 함수객체에 대해서는 다음글에서 다루겠다.


by 릭디아스 | 2009/03/30 15:48 | C++ 프로그래밍 | 트랙백 | 덧글(0)
FOR_EACH : 컨테이너에 대한 사용자 정의함수의 구동

컨테이너 원소의 각각에 특정 사용자 정의 함수를 거치도록 하여 원소를 조작 제어할수있는 함수로 for_each가 있다

for_each( X.begin(), X.end(), FUNC);

첫번째와 두번째의 인자로 컨테이너의 범위를 주고 세번째 인자로 사용자 정의 함수명을 주면 범위에 있는
모든 원소들에 대해서 FUNC를 호출한다.


#include "stdafx.h"

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

//들어온 인자를 단순출력해주는 사용자 정의 함수
void print(int elem)
{
 cout << elem << ' ';
}

int _tmain(int argc, _TCHAR* argv[])
{
 vector<int> a;

 for(int i = 1; i <= 9; ++i)
  a.push_back(i);

 //for_each알고리즘은 범위에 대해 사용자 정의함수를 동작한다
 //각각의 원소에대해 단순출력하는 함수를 동작하게 된다.

 for_each(a.begin(), a.end(), print);
 cout << endl;
 return 0;
}

by 릭디아스 | 2009/03/30 13:52 | C++ 프로그래밍 | 트랙백 | 덧글(0)
PRINT_ELEMENTS : 사용자 정의 제네릭함수

단지 컨테이너의 내용을 출력하기위해서 매번
 
typename T::iterator pos;
for(pos = X.begin(); pos != X.end(); ++pos)
 std::cout << *pos << ' ';

등의 코딩을 하는 것은 귀찮은 일이다.
PRINT_ELEMENTS라는 사용자정의 함수는 단지

PRINT_ELEMENTS( container, "원소를 출력하기전에 내보낼메시지");

라는 간단한 표기로 컨테이너의 모든 요소를 표기할 수 있다.

#include "stdafx.h"

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

template <class T>
inline void PRINT_ELEMENTS(const T& coll, const char* optcstr="")
{
 typename T::const_iterator pos;

 std::cout << optcstr;
 for(pos = coll.begin(); pos != coll.end(); ++pos)
  std::cout << *pos << ' ';

 std::cout << std::endl;
}


int _tmain(int argc, _TCHAR* argv[])
{
 list<int> a;

 for(int i = 1; i <= 6; ++i)
 {
  a.push_front(i);
  a.push_back(i);
 }

 PRINT_ELEMENTS(a, "all elements : ");


 
 return 0;
}


본홈페이지의 예제에는 앞으로 PRINT_ELEMENTS를 사용한다. print.cpp라는 파일을 따로 두어 include 해서 사용하기로한다.

by 릭디아스 | 2009/03/30 13:40 | C++ 프로그래밍 | 트랙백 | 덧글(0)
REMOVE : 원소의 제거

remove()알고리즘은 주어진 범위에서 특정 원소를 제거한다. 이하의 예제대로 하면 제거는 하되 컨테이너의 사이즈는 줄지않는다.
그러나 이알고리즘은 set과 map과같은 연관컨테이너에서는 동작하지않는다. 만약 연관컨테이너를 사용하고자 한다면 이글 하단부의 예제를 참조하라.

#include "stdafx.h"

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
 list<int> a;

 for(int i = 1; i <= 6; ++i)
 {
  a.push_front(i);
  a.push_back(i);
 }

 cout << "pre : ";
 copy(a.begin(), a.end(), ostream_iterator<int>(cout, " "));
 cout << endl;

 //3을 값으로 가지는 모든 원소들을 제거한다.
 remove(a.begin(), a.end(), 3);

 cout << "post : ";
 copy(a.begin(), a.end(), ostream_iterator<int>(cout, " "));
 cout << endl;

 //3이 제거는 되었지만 원소의 개수는 그대로 남아있다.
 return 0;
}

다음의 예제는 진정한 의미의 리무브를 수행하게 된다. 특정키를 가지는 원소를 제거하고 컨테이너를 리사이즈하게된다.

#include "stdafx.h"

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
 list<int> a;

 for(int i = 1; i <= 6; ++i)
 {
  a.push_front(i);
  a.push_back(i);
 }

 cout << "pre : ";
 copy(a.begin(), a.end(), ostream_iterator<int>(cout, " "));
 cout << endl;

 //3을 값으로 가지는 모든 원소들을 제거한다.
 //새로운 끝을 보관한다.
 list<int>::iterator end = remove(a.begin(), a.end(), 3);

 cout << "number of removed elements : " << distance(end, a.end()) << endl;

 //'제거된' 원소들을 제거한다.
 a.erase(end, a.end());

 cout << "post : ";
 copy(a.begin(), a.end(), ostream_iterator<int>(cout, " "));
 cout << endl;


 return 0;
}

////////////////////////////////////////////////////////////

아래의 코드는 위에서 설명했던 작업을 단 한줄에 가능하게 해준다.
////////////////////////////////////////////////////////////

#include "stdafx.h"

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
 list<int> a;

 for(int i = 1; i <= 6; ++i)
 {
  a.push_front(i);
  a.push_back(i);
 }

 cout << "pre : ";
 copy(a.begin(), a.end(), ostream_iterator<int>(cout, " "));
 cout << endl;

 a.erase(remove(a.begin(), a.end(), 3), a.end());

 cout << "post : ";
 copy(a.begin(), a.end(), ostream_iterator<int>(cout, " "));
 cout << endl;


 return 0;
}

위와같이 remove()알고리즘은 범용적인대신 최적화되지 못한다. 예를들어 list에 대해 remove()를 사용하는 것보다는
list가 제공하는 remove 맴버함수를 사용하면 좋은 성능을 제공한다.


#include "stdafx.h"

#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
 list<int> a;

 for(int i = 1; i <= 6; ++i)
 {
  a.push_front(i);
  a.push_back(i);
 }

 //값 3을 가진 모든 원소를 제거할때..

 //나쁜성능
 a.erase(remove(a.begin(), a.end(), 3), a.end());

 //좋은성능
 a.remove(4);


 
 return 0;
}

---------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------------------------
연관컨테이너에서의 원소삭제 또한 간단하다.

#include "stdafx.h"

#include <iostream>
#include <set>
#include <algorithm>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
 set<int> a;
 for(int i = 1; i <= 9; ++i)
  a.insert(i);

 //모든 원소들을 출력한다
 copy(a.begin(), a.end(), ostream_iterator<int>(cout, " "));
 cout << endl;

 //3의 값을 가지는 모든 원소를 제거한다
 //연관컨테이너는 remove()알고리즘은 동작하지않는다
 //멤버함수 erase()를 호출한다

 int num = a.erase(3);
 
 //제거된 원소를 출력한다.
 cout << "number of removed elements: " << num << endl;

 //수정된 모든 원소들을 출력한다.
 copy(a.begin(), a.end(), ostream_iterator<int>(cout, " "));
 cout << endl;
 return 0;
}

by 릭디아스 | 2009/03/30 12:57 | C++ 프로그래밍 | 트랙백 | 덧글(0)
<< 이전 페이지 다음 페이지 >>