Δευτέρα 12 Δεκεμβρίου 2011

DirectX3d 10 part 3 (matrices, vertex shader)

Matrix στα μαθηματικά είναι ένας δισδιάστατος πινακας n επι m που έχει πολλές εφαρμογές. Στο directx χρησιμοποιούμε matrix 4χ4 (πχ float matrix[4][4], D3DXMATRIX matrix κλπ) για τον μετασχηματισμό των meshes.




Matrix
Λοιπόν, το matrix στα ελληνικά μήτρα κάνει πραγματικά την δούλευα μιας μήτρας. Βάζουμε ένα Α σχήμα  πατάμε την μήτρα και βγαίνει ένα σχήμα Β.

Τι γίνεται στο παραπάνω;
Για αρχή έχουμε κάποια σημεία (point3d ή vertex ή πες τα όπως θες, αρκεί να περιγραφεί μια θέση σε 3δ κόσμο) που περιγράφουν το σχήμα Α
A =
v1(-1,3,0)
v2(3,3,0)
v3(-1,-1,0)
v4(3,-1,0)

Έπειτα έχουμε το matrix Scale Matrix το οποίο είναι
(Αριστερά μαθηματική απεικόνιση δεξιά c++ 2d array)
Όπου:
x = σμίκρυνση/μεγέθυνση του x άξονα (v1,v2)
y = σμίκρυνση/μεγέθυνση του y άξονα (v1,v3)
z =  σμίκρυνση/μεγέθυνση του z άξονα (v1,v3)
Για μεγέθυνση πρεπει x,y,z > 1
Για σμίκρυνση 0 < x,y,z < 1
Το scale γίνεται ποσοστιαία. πχ αν θέλομε να το μικρύνουμε στο μισό τότε x=y=z= 1/2, για να το διπλασιάσουμε x=y=z= 1 * 2.

Στο παράδειγμα μου το σχήμα Β είναι 3 φορές μικρότερο, άρα έχουμε x=y=z = 1/3
το matrix γίνεται



 Τώρα το μόνο που μένει να κάνουμε είναι να πολλαπλασιάσουμε την μήτρα με όλα τα σημεία
Το παραπάνω το εφαρμόζουμε σε όλα τα vertex-position-vector-whatever
Για περισσότερα περί του πολλαπλασιασμού matrix - matrix  matrix - vector κλπ google linear matrix product.

Λοιπόν, στο(προηγούμενο) project πάμε στον constructor της C3DModelEnaTrigwnaki και κάνουμε τις παρακάτω αλλαγές

 C3DModelEnaTrigwnaki(ID3D10Device *pRefDevice)
  : pRefDevice(pRefDevice)
 {
  pRefDevice->AddRef();
  a = 0.0f;
  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 };

  
  float scale[4][4] =
  {
   { .33f, 0.0f, 0.0f, 0.0f },
   { 0.0f, .33f, 0.0f, 0.0f },
   { 0.0f, 0.0f, .33f, 0.0f },
   { 0.0f, 0.0f, 0.0f, 1.0f }
  };
  for(int i = 0 ; i < 5; i++)
  {
   D3DXVec3TransformCoord(&vertices[i].position,&vertices[i].position,&D3DXMATRIX((float*)scale));
  }
  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);

  
 }

.
scale x=y=z= 1/3

scale x =1/3 y = 1.5  z = 1

Φυσικά το directx έχει έτοιμες αυτές τις συναρτήσεις που φτιάχνουν matrix.
πχ

(Αριστερά πριν, δεξιά μετά)
D3DXMatrixRotationZ (X/Y)
 D3DXMatrixScaling
 D3DXMatrixTranslation
Κλπ.



Matrix & VertexShader
Μέχρι τώρα εφαρμόσαμε το matrix πριν βάλουμε τον vertex buffer στο IA stage. Τώρα ήρθε η ώρα να το εφαρμόσουμε στο VS stage. Δηλαδή ο μετασχηματισμός να γίνεται στη GPU!

Στο effect file θα βάλουμε ενα global matrix (float4x4) με όνομα... MyTransformation. Έπειτα θα πολλαπλασιαστούμε το input του VS με το MyTransformation.


float4x4 MyTransformation;
struct Vertex
{
 float4 pos : SV_POSITION ;
 float4 col : COLOR;
};
Vertex MyVertexShader( float4 Pos : POSITION,float4 Col : COLOR )
{
 Vertex v = (Vertex)0;
 v.pos = mul(Pos,MyTransformation);
 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() ) );
    }
}
.
Ωραία φτιαξαμε ένα matrix, αλλάξαμε το VS έτσι ώστε ολα τα position που δέχεται να τα πολλαπλασιάζει με το matrix δηλαδή να μετασχηματίζει το σχήμα και μας λείπει... ένα τρόπο με το οποίο θα αλλάζουμε το MyTransformation μέσα απο το πρόγραμμα.
Εδώ έρχεται το interface ID3D10EffectMatrixVariable που δημιουργεί μια γέφυρα απο μια variable που είναι στην GPU σε μια άλλη που είναι στο πρόγραμμα μας.

C3DModelEnaTrigwnaki

#pragma once
#include "dxandwindow.h"
#include 


class C3DModelEnaTrigwnaki
{
 struct Vertex
 {
  D3DXVECTOR3 position;
  D3DXVECTOR4 color;
 };
 D3D10_TECHNIQUE_DESC   techDesc;
 ID3D10Device     *pRefDevice;
 ID3DX10Mesh      *pMesh;
 ID3D10Effect     *pEffect;
 ID3D10InputLayout    *pLayout;
 ID3D10EffectTechnique   *pTech;
 /////////////////////////////////////////////////////
 ID3D10EffectMatrixVariable  *pMyTransformationVar;
 float       r;
 /////////////////////////////////////////////////////
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);
  //////////////////////////////////////////////////
  this->pMyTransformationVar = this->pEffect->GetVariableByName("MyTransformation")->AsMatrix();
  r = .0f;
  //////////////////////////////////////////////////
  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()
 {
  /////////////////////////////////////////////////
  r+= 0.01;
  if(r > 2.f * D3DX_PI) r =.0f;
  D3DXMATRIX MyTransformation;
  D3DXMatrixRotationZ(&MyTransformation,r);

  pMyTransformationVar->SetMatrix(MyTransformation);

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


  for(UINT pass = 0 ; pass < this->techDesc.Passes; pass++)
  {
   pTech->GetPassByIndex(pass)->Apply(0);
   pMesh->DrawSubset(0);
  }

 
 }
};
.
Και ναι, έχουμε κίνηση!

Πως γίνεται αυτό;
Λοιπόν, ο vertex buffer που περιγράφει το σχήμα μας είναι constant στη vram. Πριν ζωγραφιστεί στο surface πρέπει να περάσει απο μερικά στάδια του directx pipeline. Στη render (στην παραπάνω κλάση) έχουμε μια μεταβλητή r η οποια αυξάνεται απο 0 σε 2π (2π πλήρη περιστροφή, δεν ειναι μοίρες αλλα ακτίνια), έπειτα φτιάχνουμε μια μήτρα η οποία είναι για περιστροφή. Αυτή τη μήτρα την βάζουμε στη vram (sto effect μας) μέσου τη γέφυρα pMyTransformationVar. (η μεταφορά γίνεται όταν καλέσουμε την pTech->GetPassByXX()->Apply(0))
Μέτα καλούμε την DrawSubset η οποία αρχίζει τα stage. Όταν φτάσει στο VS τότε γίνεται η παραμόρφωση του σχήματος βάση της μήτρας.

Επειδή δεν έχει γινει ακόμα το swap των back-front buffers, μπορούμε το ιδιο σχήμα να το ξαναζωγραφίσουμε!

Αλλάζοντας μονο την render




 void Render()
 {
  /////////////////////////////////////////////////
  r+= 0.05;
  if(r > 2.f * D3DX_PI) r =.0f;
  D3DXMATRIX MyTransformation;
  D3DXMatrixRotationZ(&MyTransformation,r);

  pMyTransformationVar->SetMatrix(MyTransformation);

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


  for(UINT pass = 0 ; pass < this->techDesc.Passes; pass++)
  {
   pTech->GetPassByIndex(pass)->Apply(0);
   pMesh->DrawSubset(0);
  }

  D3DXMATRIX scale,trans,rot;
  D3DXMatrixTranslation(&trans,0.6f,0.6f,0.0);
  D3DXMatrixScaling(&scale,1/4.0f,1/4.0f,1.0f);
  D3DXMatrixRotationZ(&rot,r);

  MyTransformation = scale * trans * rot;

  pMyTransformationVar->SetMatrix(MyTransformation);
   
  for(UINT pass = 0 ; pass < this->techDesc.Passes; pass++)
  {
   pTech->GetPassByIndex(pass)->Apply(0);
   pMesh->DrawSubset(0);
  }

  
  D3DXMatrixTranslation(&trans,-0.6f,0.6f,0.0);
  MyTransformation = scale * trans * rot;

  pMyTransformationVar->SetMatrix(MyTransformation);
   
  for(UINT pass = 0 ; pass < this->techDesc.Passes; pass++)
  {
   pTech->GetPassByIndex(pass)->Apply(0);
   pMesh->DrawSubset(0);
  }
 }
.

Πλέον νομίζω οτι το matrix αρχίζει να βγάζει νόημα.
Με 16 αριθμούς μπορεις να παραμορφώσεις όπως θες ενα σχήμα με 10-100-1000-10κ-100κ σημεία.
Στο πάνω παράδειγμα:
Το πρώτο τρίγωνο μένει ως έχει απο πριν.
Το δεύτερο το περιστρέφουμε, το μικραίνουμε και τέλος το μετακινούμε. Το ίδιο και το τρίτο.

Matrix & σωστή σειρά πολλαπλασιασμού
Αν μπήκατε στο κόπο να κάνετε το παραπάνω, τότε αυτό που είδατε στην οθόνη σας δεν έχει καμιά σχέση με το video. Αυτό επειδή στο κωδικά εχω βάλει τον πολλαπλασιασμό των matrices με διαφορετική σειρά. Ο λογος; Να'χω να γράψω κατι παραπάνω σε αυτή την παράγραφο :p .

Για να βγάλουμε το transformation απο συνδυασμό συγκεκριμένων transformations πχ scale rotation etc πρέπει να ξέρουμε τι θέλουμε να κάνουμε και πως είναι ο vertex buffer δηλαδή ποιο είναι το κέντρο κέντρο του σχήματος.

Λοιπόν, ας κάψουμε κανα κύτταρο!!!

Το σχήμα που εχω είναι

Στο παραπάνω παράδειγμα περιστρέφω το σχήμα με βάση του άξονα z ο οποίος ειναι στο κέντρο του τριγώνου. Με αυτό το αποτέλεσμα

MyTransformation = rot

Αν εγώ θελω να περιστρέφεται σε κάποιο άλλο σημείο της οθόνης; Φαίνεται απλό, MyTransformation = rot * trans και ναι αυτό είναι.

MyTransformation = rot * trans


Τώρα το τρίγωνο περιστρέφεται λίγο πιο κάτω. Ναι αλλά αν θέλω να περιστρέφεται με το άξονα z να είναι στη κορυφή του τριγώνου; Το ίδιο με το προηγούμενο αλλά με άλλη σειρά.

MyTransformation = trans * rot

Γιατί σημαίνει αυτό; -στη c++ οι πράξεις εκτελούνται απο αριστερά προς τα δεξιά πχ int o = 1 + 2 + 3  μεταφράζεται σε int o = ( (1 + 2 ) + 3 )- Για να το εξηγήσω αυτό θα το πάω γεωμετρικά. Έστω οτι η παράσταση rot * trans  δεν πολλαπλασιάζει αλλά εκτελεί τους μετασχηματισμούς εναν εναν σειριακά.
1 rot -> περίστρεψε το σχήμα 
2 trans -> μετακίνησε το σχήμα
με αυτά βγαίνει η πρόταση "γύρνα το τρίγωνο και μετά μετακίνησε το πιο κάτω"
σε αντίθεση με
1 trans μετακίνησε το σχήμα
2 rot περίστρεψε το σχήμα
που η πρόταση γίνεται "μετακίνησε το και μετά γύρνα το" 

Με τις παραπάνω προτάσεις σκεφτείτε τι θα κάνατε αν είχατε μπροστά σας ενα πιάτο με ενα σπιρτόκουτο πανω στο κέντρο του πιάτου.
αν σας έλεγα "γύρνα το πιάτο και μετά τραβά το σπιρτόκουτο προς τα εσένα" θα βλέπατε το ίδιο μοτίβο με το πρώτο vid(rot*trans)
αν όμως σας έλεγα "τραβά το σπιρτόκουτο και γύρνα το πιάτο" θα βλέπατε το ίδιο πράγμα με το δεύτερο vid (trans * rot)

Με λίγα λόγια το κέντρο βάρος ενοε σχήματος εδω είναι το x,y,z 0 το οποίο δεν εγγυάται οτι είναι πραγματικά το κέντρο βάρος του σχήματος! Φυσικά η κλάση ID3DX10Mesh έχει μια επιλογή που ξαναφτιαχνει το σχήμα με το κέντρο βάρος να είναι το x,y,z 0.



Ας το κάνουμε λίγο πιο πολύπλοκο, ας φτιάξουμε τον ήλιο και την γη. ( με τα τριγωνάκια, προς θεού οχι ακόμα 3d models)

Για τον ήλιο scale * rot απλό.
για την γη scale * rot * trans * rot μπέρδεμα, στην αρχή την μικραίνουμε, μετά την περιστρέφουμε από τον εαυτό της, την μετακινούμε μακριά από τον ήλιο, και τέλος την περιστρέφουμε γύρο από τον ήλιο.

οκ οκ δεν ήταν και τόσο πολύπλοκο, ας βάλουμε και τη σελήνη
Τα άλλα έχουν ως έχουν, σελήνη = scale * rot * trans * rot * trans * rot

Έχουμε και λέμε μίκρυνε την, περιστροφή απο τον εαυτό της, μετακίνησε την στη γη, περιστροφή μαζί με τι γη, μετακίνησε την μακριά απο την γη, περιστροφή γύρο από την γη :p


Στο παραπάνω το μόνο που άλλαξε είναι το Render της C3DModelEnatrigvnaki 

 void Render()
 {
  r+= 0.025;
  if(r > 2.f * D3DX_PI) r =.0f;
  D3DXMATRIX MyTransformation;
  D3DXMATRIX scale,scale1,trans,trans1,rot;

  D3DXMatrixTranslation(&trans,0.0f,0.7f,0.0);
  D3DXMatrixTranslation(&trans1,0.0f,0.2f,0.0);
  D3DXMatrixScaling(&scale,1/4.0f,1/4.0f,1.0f);
  D3DXMatrixScaling(&scale1,1/2.0f,1/2.0f,1.0f);
  D3DXMatrixRotationZ(&rot,r);

  //ilios
  MyTransformation =
   scale1 * //mikrine ton oxi poly
   rot ;  // peristrofi apo ton eayto toy

  pMyTransformationVar->SetMatrix(MyTransformation);
   
  for(UINT pass = 0 ; pass < this->techDesc.Passes; pass++)
  {
   pTech->GetPassByIndex(pass)->Apply(0);
   pMesh->DrawSubset(0);
  }

  //gi
  MyTransformation =
   scale *  // mikrine tin
   rot *  // peristofi apo to eayto tis
   trans *  // metakinise tin
   rot ;  // peristrofi apo ton ilio
  
  pMyTransformationVar->SetMatrix(MyTransformation);
   
  for(UINT pass = 0 ; pass < this->techDesc.Passes; pass++)
  {
   pTech->GetPassByIndex(pass)->Apply(0);
   pMesh->DrawSubset(0);
  }


  //selini
  D3DXMatrixScaling(&scale1,1/6.0,1/6.0,1.0);
  MyTransformation =
   scale1 *  // mikrine tin
   rot *   // peristofi apo to eayto tis
   trans1 *  // metakinise tin sti gi
   rot *   // peristrofi mazi me ton ilio
   trans *   // metakinise tin ligo pio makria apo ti gi
   rot;   // peristrofi giro apo tob ilio
  
  pMyTransformationVar->SetMatrix(MyTransformation);
   
  for(UINT pass = 0 ; pass < this->techDesc.Passes; pass++)
  {
   pTech->GetPassByIndex(pass)->Apply(0);
   pMesh->DrawSubset(0);
  }
.
Λόγου οτι στο επόμενο το project θα αλλάξει αρκετά να ο source code 

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

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