Σάββατο 10 Δεκεμβρίου 2011

DirectX3d 10 part 2 (ζωγραφίζοντας στον backbuffer)

Shaders, meshes,matrices




Στο προηγούμενο το μόνο που κάναμε είναι τα swaps των surface buffers. Σε αυτό θα ζωγραφίσουμε. Έγραψα οτι το ID3D10device είναι ο σκελετός της μηχανής μας. Από μόνη της δεν μπορεί να κάνει κάτι, πρέπει να την προγραμματίσουμε, αυτό που προγραμματίζεται είναι το directx pipeline. Στο προηγούμενο προγραμματίσαμε μόνο δυο σταδία, το output merger και το rasterize το οποίο δεν χρειαζόταν αλλά δεν βαριέσαι.
Τα στάδια που θα προγραμματίσουμε επιπλέον είναι Input Assembler (IA), Vertex Shader (VS) Pixel Shader (PS).

Ο κώδικας θα είναι η συνεχεία του προηγούμενου, για να γίνει πιο εύκολο το καινούριο κομμάτι θα το γράψω όλο σε μια κλάση με όνομα (και πράμα) C3DModelEnaTrigwnaki.

Λοιπόν, βάζουμε στο project άλλα δυο αρχεία
enatrigwnaki.h που θα έχει την κλάση
enatrigwnaki.fx το οποίο θα είναι ascii

Το update του main.cpp


#include "dxandwindow.h"
#include "enatrigwnaki.h"//<--

LPCWSTR lpszWndClsName = L"{BAF17182-20D1-46EB-8CAB-B4AAF5388552}";
LPCWSTR lpszWndTitle = L"Hi!";
HWND     gWnd;
int      Height = 600;
int      Width = 800;


ID3D10Device    *pDevice;
IDXGISwapChain    *pSwapChain;
ID3D10RenderTargetView  *pRenderView;

C3DModelEnaTrigwnaki  *pMyModel;//<--

HRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
int InitDevice(HWND hWnd,int w,int h);
int InitModels();//<--
int ReleaseDevice();
int ReleaseModels();//<--
void RenderGame();



int WINAPI wWinMain(HINSTANCE hInst,HINSTANCE,LPWSTR,int nShow)
{
 WNDCLASS wc;
 MSG msg;

 ZeroMemory(&wc,sizeof(wc));

 wc.lpfnWndProc  = WndProc;
 wc.lpszClassName = lpszWndClsName;
 wc.hCursor   = LoadCursor(NULL,IDC_ARROW);


 if(!RegisterClass(&wc))
  return 1;
 gWnd = CreateWindow(lpszWndClsName,lpszWndTitle,
  WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,0,Width,Height,
  NULL,NULL,hInst,NULL);
 if(!gWnd)
  return 1;
 ShowWindow(gWnd, nShow);


 while(GetMessage(&msg,0,0,0) > 0)
 {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
 }

 return 0;
}
HRESULT CALLBACK WndProc(HWND hWnd,UINT msg,WPARAM wParam, LPARAM lParam)
{
 switch(msg)
 {
 case WM_TIMER:
  RenderGame();
  break;
 case WM_CREATE:
  if(InitDevice(hWnd,Width,Height))
   return -1;
  if(InitModels())//<--
   return -1;
  SetTimer(hWnd,1,1000/100,NULL);
  break;
 case WM_CLOSE:
  ReleaseModels();//<--
  ReleaseDevice();
  PostQuitMessage(0);
  break;

 default:
  return DefWindowProc(hWnd,msg,wParam,lParam);
 }
 return 0;
}
int InitDevice(HWND hWnd,int w,int h)
{

 ID3D10Texture2D    *pBackBuffer;
 DXGI_SWAP_CHAIN_DESC  swapChainDesc;

 
 ZeroMemory(&swapChainDesc,sizeof(swapChainDesc));
 swapChainDesc.BufferCount   = 1;
 swapChainDesc.BufferUsage   = DXGI_USAGE_RENDER_TARGET_OUTPUT;
 swapChainDesc.SampleDesc.Count  = 1;
 swapChainDesc.OutputWindow   = hWnd;
 swapChainDesc.Windowed    = true;

 swapChainDesc.BufferDesc.Height  = h;
 swapChainDesc.BufferDesc.Width  = w;
 swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
 swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
 swapChainDesc.BufferDesc.Format  = DXGI_FORMAT_R8G8B8A8_UNORM;

 if(FAILED(D3D10CreateDeviceAndSwapChain(
  NULL,D3D10_DRIVER_TYPE_HARDWARE,NULL,NULL,
  D3D10_SDK_VERSION,&swapChainDesc,&pSwapChain,&pDevice)))
 {
  return 1;
 }

 if(FAILED(pSwapChain->GetBuffer(0,__uuidof(ID3D10Texture2D),(void**)&pBackBuffer)))
 {
  pSwapChain->Release();
  pDevice->Release();
  return 1;
 }
 
 if(FAILED(pDevice->CreateRenderTargetView(pBackBuffer,0,&pRenderView)))
 {
  pSwapChain->Release();
  pDevice->Release();
  pBackBuffer->Release();
  return 1;
 }
 pBackBuffer->Release();


 pDevice->OMSetRenderTargets(1,&pRenderView,NULL);
 

 D3D10_VIEWPORT vp;
 vp.Height = h;
 vp.Width = w;
 vp.MaxDepth = 1.0f;
 vp.MinDepth = 0.0f;
 vp.TopLeftX = 0;
 vp.TopLeftY = 0;
 pDevice->RSSetViewports(1,&vp);
 
 return 0;
}
int ReleaseDevice()
{
 pRenderView->Release();
 pSwapChain->Release();
 pDevice->Release();
 return 0;
}

void RenderGame()
{
 pDevice->ClearRenderTargetView(pRenderView,D3DXCOLOR(0.0f,0.0f,0.30f,1.0f));
 pMyModel->Render();
 pSwapChain->Present(1,0);
}

int InitModels()
{
 try
 {
  pMyModel = new C3DModelEnaTrigwnaki(pDevice);
 }
 catch(std::exception e)
 {
  MessageBoxA(NULL,e.what(),"Error",0);
  return 1;
 }
 
 return 0;
}
int ReleaseModels()
{
 delete pMyModel;
 return 0;
}
.

enatrigwnaki.h

#pragma once
#include "dxandwindow.h"
#include <exception>


class C3DModelEnaTrigwnaki
{
 struct Vertex
 {
  D3DXVECTOR3 position;
  D3DXVECTOR4 color;
 };
 D3D10_TECHNIQUE_DESC   techDesc;
 ID3D10Device     *pRefDevice;
 ID3DX10Mesh      *pMesh;
 ID3D10Effect     *pEffect;
 ID3D10InputLayout    *pLayout;
 ID3D10EffectTechnique   *pTech;
public:
 C3DModelEnaTrigwnaki(ID3D10Device *pRefDevice)
  : pRefDevice(pRefDevice)
 {
  pRefDevice->AddRef();

  D3D10_INPUT_ELEMENT_DESC layout[]=
  {
   { "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 }
  };
  Vertex vertices[] =
  {
   { D3DXVECTOR3( 0.0f, 0.5f, 0.0f ),D3DXVECTOR4(1.0f,0.0f,0.0f,1.0f) },
   { D3DXVECTOR3( 0.5f, -0.5f,0.0f ),D3DXVECTOR4(1.0f,0.0f,0.0f,1.0f) },
   { D3DXVECTOR3( -0.5f, -0.5f,0.0f ) ,D3DXVECTOR4(1.0f,0.0f,0.0f,1.0f)},

   { D3DXVECTOR3( 0.3f, -0.3f,0.0f ),D3DXVECTOR4(1.0f,1.0f,0.0f,1.0f) },
   { D3DXVECTOR3( -0.3f, -0.3f,0.0f ) ,D3DXVECTOR4(1.0f,1.0f,0.0f,1.0f)},
  };
  UINT indecis[] = { 0 , 1 , 2, 0 , 3 , 4 };

  D3D10_PASS_DESC passDesc;
  if(FAILED(D3DX10CreateMesh(
   this->pRefDevice,
   layout,_countof(layout),
   layout[0].SemanticName,
   _countof(vertices),
   _countof(indecis) / 3u,
   D3DX10_MESH_32_BIT,
   &this->pMesh)))
  {
   //error
   throw std::exception("Mesh init fail");
  }

  if(FAILED(this->pMesh->SetVertexData(0,vertices)))
  {
   //error
   throw std::exception("Mesh init fail @SetVertexData");
  }

  if(FAILED(this->pMesh->SetIndexData(indecis,_countof(indecis))))
  {
   //error
   throw std::exception("Mesh init fail @SetIndexData");
  }

  if(FAILED(this->pMesh->CommitToDevice()))
  {
   //error
   throw std::exception("Mesh init fail @CommitToDevice");
  }
  ID3D10Blob *pCompileError = NULL;
  if(FAILED(D3DX10CreateEffectFromFile(
   L"EnaTrigwnaki.fx",NULL,NULL,
   "fx_4_0",
   D3D10_SHADER_ENABLE_STRICTNESS,0,
   pRefDevice,NULL,NULL,&this->pEffect,
   &pCompileError,NULL)))
  {
   char tmp[1024];
   ::strcpy(tmp,"Effect compile error: ");
   ::strncpy(tmp + strlen(tmp),(char*)pCompileError->GetBufferPointer(),_countof(tmp) - strlen(tmp));
   pCompileError->Release();
   throw std::exception(tmp);
  }
  this->pTech = this->pEffect->GetTechniqueByIndex(0);
  if(FAILED(pTech->GetPassByIndex(0)->GetDesc(&passDesc)))
  {
   throw std::exception("Effect error @Tech::GetPassByIndex::GetDesc");
  }
  
  if(FAILED(pRefDevice->CreateInputLayout(
   layout, _countof(layout),
   passDesc.pIAInputSignature,passDesc.IAInputSignatureSize,
   &this->pLayout)))
  {
   throw std::exception("Layout init fail @ID3D10Device::CreateInputLayout");
  }
  
  if(FAILED(pTech->GetDesc(&this->techDesc)))
  {
   throw std::exception("Error @pTech::GetDesc");
  }
  
  pRefDevice->IASetInputLayout(pLayout);
  pRefDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
 }
 ~C3DModelEnaTrigwnaki()
 {
  if(this->pEffect)
  {
   this->pEffect->Release();
   this->pEffect = NULL;
  }
  if(this->pLayout)
  {
   this->pLayout->Release();
   this->pLayout = NULL;
  }
  if(this->pMesh)
  {
   this->pMesh->Release();
   this->pMesh = NULL;
  }
  pRefDevice->Release();
 }
 void Render()
 {
  for(UINT pass = 0 ; pass < this->techDesc.Passes; pass++)
  {
   pTech->GetPassByIndex(pass)->Apply(0);
   pMesh->DrawSubset(0);
  }
 
 }
};
.
enatrigwnaki.fx

struct Vertex
{
 float4 pos : SV_POSITION ;
 float4 col : COLOR;
};
Vertex MyVertexShader( float4 Pos : POSITION,float4 Col : COLOR )
{
 Vertex v = (Vertex)0;
 v.pos = Pos;
 v.col = Col;
 return v;
}

float4 MyPixelShader(Vertex v) : SV_Target
{
    return v.col;    
}

technique10 Tech
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, MyVertexShader() ) );
        SetPixelShader( CompileShader( ps_4_0, MyPixelShader() ) );
    }
}
.

Στην main.cpp δεν άλλαξαν πολλά πράγματα. Άπλα φτιάχνουμε ενα αντικείμενο (C3DModelEnaTrigwnaki) και καλούμε την συνάρτηση C3DModelEnaTrigwnaki::Render μέσα στη GameRender μετά το clear και πριν το swap (present).

Στη κλάση C3DModelEnaTrigwnaki έχουμε 4 νέα interfaces και ένα struct Vertex, τα δυο ανήκουν στο IA stage τα άλλα δυο στο PS/VS stage.

Vertex
Το vertex είναι μια πρωτότυπη δομή που περιέχει μόνο σημεία. Το απλούστατο Vertex είναι ένα σημείο, δηλαδή ένας float ούτε καν μια δομή αυτό φυσικά αναφέρεται σε μονοδιάστατο κόσμο. Για 3d θέλουμε τουλάχιστον 3 σημεία x,y,z. Το Vertex στο παράδειγμα μου έχει δυο Vectors -D3DXVECTOR2/3/4 είναι δομές τύπου σημείου με αρκετούς operators για μαθηματικές πράξεις- ο πρώτος έχει x,y,z το οποίο αναφέρεται στο σημείο του τρισδιάστατου κόσμου και ο δεύτερος x,y,z,w (r,g,b,a) που αναφέρεται στο χρώμα.


Στο dx ζωγραφίζουμε με σημεία με το ελάχιστο σχήμα να είναι ενα τρίγωνο και το πιο πολύπλοκο να αποτελείτε απο τρίγωνα. Πχ στη πάνω εικόνα για να φτιάξουμε ενα τρίγωνο θέλουμε 3 vertices (v1,v2,v4) σε αντίθεση με ενα τετράγωνο που θέλει 6 και όχι 4 vertices (v1,v2.v4,v2,v3,v4). Όλα αυτά τα vertices είναι vertex buffer.

Να και ένα gif (ειμαι καλλιτέχνης :p)

Αυτή η μέθοδος ονομάζεται triangle list topology. Ο λόγος που εφαρμόζεται αυτή η μέθοδος είναι τα μαθηματικά.
Βλέπουμε οτι ο vertex buffer έχει 9 vertices, τα 3 παραπάνω είναι απλά επαναλαμβανόμενα. Για να αποφύγουμε την επανάληψη των vertices πρέπει να βάλουμε και έναν index buffer. Ο index buffer είναι ένα array από integers που μα δείχνει ποιο vertex είναι το επόμενο. Πχ για το παραπάνω σχήμα με το vertex buffer 
v1,v2,v3,v4,v5
θα θέλαμε το παρακάτω index buffer
1 3 4    ---> το τρίγωνο v1,v3,v4
1 4 2    ---> το τρίγωνο v1,v4,v2 κλπ
2 4 5

Ο vertex buffer μαζί με τον index buffer ονομάζεται mesh. Το id3dx10mesh είναι ουσιαστικά ένας wrapper αυτών των buffers.

Input Assembler stage
Σε αυτό το στάδιο μεταφέρουμε τα resource από τη ram στην vram. Όπου resource vertex buffer index buffer texture shaders κλπ.
Στον constructor της c3dmodelenatrigwnaki έχουμε τον struct D3D10_INPUT_ELEMENT_DESC. Αυτή η δομή περιγράφει τον vertex struct, αυτά που πρέπει να ορίσουμε είναι:
SemanticName που είναι ένα allies για τους shaders (θα το δούμε πιο κάτω)
Format ο τύπος του στοιχειού του vertex πχ το πρώτο (position) είναι ενας vector3 ή αλλιώς 3 float
AlignedByteOffset μετά απο πόσα byte θα βρεις το εν λόγο στοιχειό. Πχ το position είναι πρώτο αρα 0 το color είναι μετα από το position αρα sizeof(position) = 12

Αυτές τις πληροφορίες τις θέλουμε για δυο λογούς
1) createMesh εκεί υπάρχει για να υπολογιστεί το μέγεθος του vertex buffer και για optimize
2) Και κυριος για να φτιάξουμε το id3d10inputlayout
id3d10inputlayout είναι το interface που θα κρατάει τις πληροφορίες για τον vertex buffer και για τους shaders

Μέτα απο το layout description έχουμε τους δυο buffers (vertex,index) και μετά απο αυτούς έχουμε την createmesh που παίρνει:
1) το instance του device
2-4) το layout description
5) πλήθος των vretices
6) πλήθος των face (ουσιαστικά έχουμε ενα, αλλά για να είναι πιο εύκολο ας πούμε οτι τρίγωνο = face)
7) πληροφορίες για άλλους buffers πχ index, attribut κλπ
8) pointer στο mesh interface

Mesh::SetVertexData/Mesh::SetIndexData εισάγουμε τους buffers στο mesh. Προσοχή δεν αποδεσμεύουμε τους buffers διότι ακόμα δεν έχουν μπει στο IA stage!

Mesh::CommitToDevice μετά απο αυτό, οι buffers έχουν μπει στο IA αρα μπορούμε να τους αποδεσμεύσουμε (αν ειναι δυναμικοί)

Αυτή είναι η διαδικασία για να βάλουμε τους buffers. Τώρα πρέπει να βάλουμε και τους shaders, παρόλο που αναφέρω τους shaders, δεν είμαστε ακόμα στο PS/VS.

CreateEffectFromFile εδώ κάνουμε compile τους shaders μας.

Effect::GetTechniqueByIndex/Name κρατάμε μια γέφυρα με τους shaders (διότι είναι στην vram)

Effect::GetTechniqueByIndex::GetPassByIndex::GetDesc παίρνουμε μια περιγραφή των shaders για να φτιάξουμε το inputLayout

Device::CreateInputLayout φτιάχνουμε το layout όλων των παραπάνω

IASetInputLayout κουμπώνουμε το layout στο pipeline
IASetPrimitiveTopology σεταρουμε την topology σε triangle list και τελος. Τώρα αν θέλουμε μπορούμε να ξανακάνουμε την παραπανω διαδικασία ξανά και ξανά και ξανα.

Effect & shaders
Οι shaders είναι μικροπράγματα τα οποία τρέχουν στην gpu. Αυτά τα γράφουμε σε HLSL και τα.
 κάνουμε compile με το directx (createEffectXXX).
Στο παράδειγμα εχω δυο shaders τα οποία αποτελούν και stage του pipeline

Vertex Shader Stage
Σε αυτό το στάδιο, περνάει όλος ο vertex buffer απο αυτή την "συνάρτηση". Το τι μπορούμε να κάνουμε θα το γράψω στο επόμενο που θα έχει και matices.

Pixel Shader Stage
Αυτό είναι το προτελευταίο στάδιο, εδώ γίνεται η απόδοση του χρώματος (για κάθε pixel). Προς το παρόν επιστρέφουμε οτι παίρνουμε.


Επιστρέφοντας στο c3dmodelenatrigwnaki, έχουμε την τελευταία συνάρτηση (Render).
pTech->GetPassByIndex(0)->Apply(0)
pMesh->DrawSubset(0) περνάμε τον vertex buffer απο το pipeline

viola

Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου