Home Page Home Page Articoli Integrare le Direct3D 8.1 con MFC Usando Visual Studio 6.0

Integrare le Direct3D 8.1 con MFC Usando Visual Studio 6.0

In questo articolo mostreremo come creare una semplice applicazione SDI (Single Document Interface), in cui mostrare un oggetto tridimensionale in formato *.X e di farlo ruotare intorno all'asse Y.
Autore: Biagio Iannuzzi Livello:
Gli esempi di integrazione tra directx 8.1 ed MFC forniti nell?SDK per Directx 8.1 non sono per niente chiari, né ci permettono di utilizzare Class Wizard per mappare nuovi messaggi, aggiungere funzioni, ecc?, con quest?ottimo tutorial faremo tutto ciò che abbiamo dettp in maniera veloce ed intuitiva. È? comunque necessario per leggere agevolmente il codice, avere conoscenze di MFC e dei criteri di funzionamento di un?applicazione Direct3d:

Il nostro obbiettivo è quello di creare una semplice applicazione SDI (Single Document Interface), in cui mostrare un oggetto tridimensionale in formato *.X e di farlo ruotare sull?asse y, il risultato finale dovrebbe essere il seguente:




Creare il progetto ed aggiungerne le librerie

Create un nuovo poogetto con Visual Studio 6.0, quindi selezionate MFC AppWizard (exe) e intitolate il progetto col nome "prova_GPI", successivamente cliccate sul tasto ok.

Selezionate il Radio Button specificando Single Document e siate sicuri che l'opzione Document/View architecture support sia selezionata. A questo punto cliccate sul bottone Finish.

Ora bisogna inserire le librerie delle DirectX necessarie per la compilazione del progetto. Prima di far questo andate sul menu Build e poi su Set Active Configuration... e selezionate l'opzione Win32 Release.

Successivamente selezionate Project dal menue poi Settings (richiamabile anche pigiando ALT+F7), selezionate quindi la cartella Link, nel campo Object/library modules aggiungete i tre file lib: d3d8.lib D3dx8.lib Winmm.lib.

Invece nella cartella C/C++ a sinistra di Link, scegliete la categoria Precompiled Headers e selezionate il radio button Automatic use of precompiled headers

ATTENZIONE: ovviamente dovranno essere ben impostate le directory in cui il compilatore andrà a cercare i files relativi agli headers ed alle librerie!!!


Implementazione del manager 3d e della classe mesh personalizzata

Definiamo la classe base Graphics che ci permetterà di effettuare l?integrazione tra Directx ed MFC, quindi dal menu cliccate su Insert e poi su New Class, si aprirà una finestra, ora da Class Type scegliete il campo Generic Class, infine nel campo Name inserite il nome CManager3D; ora nel file header della classe CManager3D digitiamo il codice seguente sostituendo la classe scritta in maniera automatica dal compilatore:


#include <d3d8.h> //dichiarazione oggetti directx
#include <D3dx8mesh.h> //gestione delle mesh x
#include "Mesh.h" //header per la gestione della classe contenente mesh *.x

class CManager3D
{
public:
CManager3D();
virtual ~CManager3D();

public:
CMesh OBJ; //oggetto mesh personalizzato, verrà spiegato in seguito
float time;

bool create(HWND hwnd, int width,int height, bool windowed);
bool init (void);
bool resize(int width, int height);
bool update(void);
bool render(void);

IDirect3DDevice8* getDevice() {return _device;}

protected:
IDirect3D8* _d3d8; //oggetti per la gestione della device 3d
IDirect3DDevice8* _device;
};


Quindi passiamo all?implementazione del file cpp:


Quindi passiamo all?implementazione del file cpp:

CManager3D:: CManager3D () //costruttore
{
_d3d8 = NULL;
_device = NULL;
}

CManager3D::~ CManager3D () //distruttore
{
OBJ.SvuotaClasse();

if (_device) {
_device->Release();
_device = NULL; }

if (_d3d8) {
_d3d8->Release();
_d3d8 = NULL; }
}


bool CManager3D::init(void)
{
D3DXMATRIX m;

D3DXMatrixIdentity( &m );

_device->SetTransform(D3DTS_WORLD, &m); //impostazioni della telecamera

D3DXVECTOR3 eye(0.0f, 0.0f, -5.0f);
D3DXVECTOR3 at(0.0f, 0.0f, 1.0f);
D3DXVECTOR3 look(0.0f, 1.0f, 0.0f);

D3DXMatrixLookAtLH(&m, &eye, &at, &look);

_device->SetTransform(D3DTS_VIEW, &m);

OBJ.CaricaFile("navicella3.x"); //file della nostra navicella, ma al posto di
//questa potete utilizzare anche gli oggetti X
//forniti con l?SDK delle direct 8.1
//ricordate di avere nella stessa path dell?eseguibile
//il file *.x e la/le texture!
_device->SetRenderState (D3DRS_LIGHTING,FALSE );

return true;
}

bool CManager3D::resize(int width, int height) //resize della scena al cambiamento delle
{ //delle dimensioni della finestra
D3DXMATRIX m;

float aspect = (float)width / (float)height;

float fov = 3.14f / 2.0f;

D3DXMatrixPerspectiveFovLH(
&m,
fov,
aspect,
1.0f,
100.0f);

if( _device )
_device->SetTransform(D3DTS_PROJECTION, &m);

return true;
}

bool CManager3D::update(void) //rotazione della mesh X al variare del tempo
{
if (OBJ.ang_y < 2.0f * D3DX_PI) //notate la logica che c?è dietro, in questo modo evitiamo OBJ.ang_y += 3.0f * time; //l?overflow della variabile reale
else
OBJ.ang_y = OBJ.ang_y - (2.0f * D3DX_PI) + (3.0f * time);

return true;
}

bool CManager3D::render(void) //renderizzazione della scena
{
if( _device )
{
_device->Clear(
0,
0,
D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, //pulizia dello z-buffer e disegno bianco dello sfondo
RGB(255,255,255) , 1.0f, 0);

_device->BeginScene();

OBJ.RenderizzaTutteLeMesh();

_device->EndScene();

_device->Present(NULL, NULL, NULL, NULL);
}

return true;
}


bool CManager3D::create(HWND hwnd, int width,int height, bool windowed)
{
_d3d8 = Direct3DCreate8(D3D_SDK_VERSION);

D3DDISPLAYMODE displayMode;
_d3d8->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &displayMode);

D3DPRESENT_PARAMETERS presentParameters;
memset(&presentParameters,0,sizeof(D3DPRESENT_PARAMETERS));

presentParameters.Windowed = windowed; //rendering in finestra
presentParameters.BackBufferCount = 1;
presentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
presentParameters.BackBufferFormat = displayMode.Format;
presentParameters.BackBufferWidth = width; //larghezza della finestra
presentParameters.BackBufferHeight = height; //altezza della finestra
presentParameters.hDeviceWindow = hwnd; //handle della finestra di visualizzazione

presentParameters.EnableAutoDepthStencil = TRUE;
presentParameters.AutoDepthStencilFormat = D3DFMT_D16; //16 bit

if (FAILED(_d3d8->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd,D3DCREATE_HARDWARE_VERTEXPROCESSING,
&presentParameters, &_device)))
_d3d8->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&presentParameters, &_device);
return true;
}


Create una nuova classe generica e chiamatela CMesh, questa farà da contenitore per gli oggetti *.X, eccovi la dichiarazione della classe:


#include <D3dx8math.h>

class CMesh
{
public:
CMesh();
virtual ~CMesh();

D3DXVECTOR3 posizione, scaling;
LPD3DXMESH mesh_geom; //conterrà le mesh caricate da file x
LPDIRECT3DTEXTURE8 *mesh_texture; //conterrà le texture caricate da file x
LPD3DXBUFFER buf_materiali, //conterrà i materiali
buf_adiacenze; //conterrà gli indici con le adiacenze
DWORD num_materiali;
D3DXMATERIAL *materiali;
D3DMATERIAL8 *mesh_materiali;
char nome_file[512];

float ang_y;
short TypeRender; //serve per settare il tipo di renderizzazione

BOOL CaricaFile (const char *buf);
BOOL RenderizzaTutteLeMesh(void);
BOOL SvuotaClasse (void);
};

//funzioni di utilità per le conversioni delle stringhe di testo e ricerca path
//si trovano tra le utility delle directx
HRESULT DXUtil_FindMediaFile(TCHAR* strPath, TCHAR* strFilename);
const TCHAR* DXUtil_GetDXSDKMediaPath();
VOID DXUtil_ConvertGenericStringToAnsi(CHAR* strDestination, const TCHAR* tstrSource, int cchDestChar = -1);
VOID DXUtil_ConvertAnsiStringToGeneric(TCHAR* tstrDestination, const CHAR* strSource, int cchDestChar = -1);


Passiamo all'implementazione della classe, ci sono degli spunti per migliorare il lavoro:


#include "Mesh.h"
#include "Manager3d.h"
extern CManager3D *manager3d;

CMesh::CMesh()
{

}

CMesh::~CMesh()
{
SvuotaClasse();
}

BOOL CMesh::CaricaFile(const char *buf) //caricamento della mesh, passiamo quindi il nome del file *.X
{
mesh_geom = NULL; //Annullamento puntatori
mesh_texture = NULL;
materiali = NULL;
mesh_materiali = NULL;

posizione.x = posizione.y = posizione.z = 0.0f;
ang_y = 0.0f;

TypeRender = D3DFILL_SOLID; //La mesh verrà renderizzata con la texture?

DWORD i;

strcpy(nome_file,buf);

TCHAR strPath[MAX_PATH];
CHAR strPathANSI[MAX_PATH];

// Cerca la path del file, e la converte in ANSI
DXUtil_FindMediaFile( strPath, nome_file );
DXUtil_ConvertGenericStringToAnsi( strPathANSI, strPath );


D3DXLoadMeshFromX( strPathANSI, D3DXMESH_SYSTEMMEM, //studiatevi le flag D3DXMESH per la gestione dei
//vertex buffer, infatti le mesh andranno nella
//memoria della scheda video?
manager3d->getDevice(), &buf_adiacenze,
&buf_materiali, &num_materiali,
&mesh_geom );

materiali = (D3DXMATERIAL*)buf_materiali->GetBufferPointer();

mesh_materiali = new D3DMATERIAL8[num_materiali];
mesh_texture = new LPDIRECT3DTEXTURE8[num_materiali];


for (i = 0; i < num_materiali; i++)
{
mesh_materiali = materiali.MatD3D;
mesh_materiali.Ambient = mesh_materiali.Diffuse;


TCHAR strPath[512];
DXUtil_FindMediaFile( strPath, materiali.pTextureFilename );

mesh_texture = NULL;

// Crea le texture
if( materiali.pTextureFilename ) // attenzione ai materiali con stessi nomi di texture?
{
TCHAR strTexture[MAX_PATH];
TCHAR strTextureTemp[MAX_PATH];
DXUtil_ConvertAnsiStringToGeneric( strTextureTemp, materiali.pTextureFilename );
DXUtil_FindMediaFile( strTexture, strTextureTemp );

D3DXCreateTextureFromFile( manager3d->getDevice(), strTexture,
&mesh_texture);
}//fine creazione texture

}//fine for

buf_materiali->Release();

return TRUE;
}

BOOL CMesh::SvuotaClasse(void) //svuotamento delle strutture che risiedono in memoria
{
if( mesh_materiali )
delete [] mesh_materiali;

if( mesh_texture )
{
for( DWORD i = 0; i < num_materiali; i++ )
{
if( mesh_texture )
mesh_texture->Release();
}
delete[] mesh_texture;
}

if( mesh_geom )
mesh_geom->Release();

return TRUE;
}


BOOL CMesh::RenderizzaTutteLeMesh(void) //l?ho chiamata così perchè un file *.x potrebbe contenere più mesh?
{
DWORD i;

//viene effettuata la traslazione e la rotazione sull?asse Y, ma potreste effettuare
//rotazioni anche sugli altri assi?
D3DXMATRIX wld, rot, Mat;
D3DXMatrixTranslation(&wld, posizione.x, posizione.y, posizione.z);
D3DXMatrixRotationY(&rot, ang_y);
D3DXMatrixMultiply(&wld, &rot, &wld);

manager3d->getDevice()->SetTransform(D3DTS_WORLD,&wld);

D3DXMatrixIdentity(&Mat);
manager3d->getDevice()->SetTransform(D3DTS_TEXTURE0,&Mat);

for (i = 0; i < num_materiali; i++)
{
manager3d->getDevice()->SetMaterial( &mesh_materiali);
manager3d->getDevice()->SetTexture( 0, mesh_texture);

//sarebbe più veloce ed indicato usare uno switch case?
if (TypeRender == D3DFILL_SOLID)
manager3d->getDevice()->SetRenderState(D3DRS_FILLMODE , D3DFILL_SOLID);
else if (TypeRender == D3DFILL_WIREFRAME)
manager3d->getDevice()->SetRenderState(D3DRS_FILLMODE , D3DFILL_WIREFRAME);
else if (TypeRender == D3DFILL_POINT)
manager3d->getDevice()->SetRenderState(D3DRS_FILLMODE , D3DFILL_POINT);

mesh_geom->DrawSubset(i); //disegno della mesh
}

return TRUE;
}


HRESULT DXUtil_FindMediaFile( TCHAR* strPath, TCHAR* strFilename )
{
HANDLE file;

if( NULL==strFilename || NULL==strPath )
return E_INVALIDARG;

// Check if the file exists in the current directory
_tcscpy( strPath, strFilename );

file = CreateFile( strPath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL );
if( INVALID_HANDLE_VALUE != file )
{
CloseHandle( file );
return S_OK;
}

// Check if the file exists in the current directory
_stprintf( strPath, _T("%s%s"), DXUtil_GetDXSDKMediaPath(), strFilename );

file = CreateFile( strPath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL );
if( INVALID_HANDLE_VALUE != file )
{
CloseHandle( file );
return S_OK;
}

// On failure, just return the file as the path
_tcscpy( strPath, strFilename );
return E_FAIL;
}

const TCHAR* DXUtil_GetDXSDKMediaPath()
{
static TCHAR strNull[2] = _T("");
static TCHAR strPath[MAX_PATH];
DWORD dwType;
DWORD dwSize = MAX_PATH;
HKEY hKey;

// Open the appropriate registry key
LONG lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
_T("Software\\Microsoft\\DirectX"),
0, KEY_READ, &hKey );
if( ERROR_SUCCESS != lResult )
return strNull;

lResult = RegQueryValueEx( hKey, _T("DX8SDK Samples Path"), NULL,
&dwType, (BYTE*)strPath, &dwSize );
RegCloseKey( hKey );

if( ERROR_SUCCESS != lResult )
return strNull;

_tcscat( strPath, _T("\\Media\\") );

return strPath;
}

//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertGenericStringToAnsi()
// Desc: This is a UNICODE conversion utility to convert a TCHAR string into a
// CHAR string. cchDestChar defaults -1 which means it
// assumes strDest is large enough to store strSource
//-----------------------------------------------------------------------------
VOID DXUtil_ConvertGenericStringToAnsi( CHAR* strDestination, const TCHAR* tstrSource,
int cchDestChar )
{
if( strDestination==NULL || tstrSource==NULL || cchDestChar == 0 )
return;

#ifdef _UNICODE
DXUtil_ConvertWideStringToAnsi( strDestination, tstrSource, cchDestChar );
#else
if( cchDestChar == -1 )
{
strcpy( strDestination, tstrSource );
}
else
{
strncpy( strDestination, tstrSource, cchDestChar );
strDestination[cchDestChar-1] = '\0';
}
#endif
}

//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertAnsiStringToGeneric()
// Desc: This is a UNICODE conversion utility to convert a CHAR string into a
// TCHAR string. cchDestChar defaults -1 which means it
// assumes strDest is large enough to store strSource
//-----------------------------------------------------------------------------
VOID DXUtil_ConvertAnsiStringToGeneric( TCHAR* tstrDestination, const CHAR* strSource,
int cchDestChar )
{
if( tstrDestination==NULL || strSource==NULL || cchDestChar == 0 )
return;

#ifdef _UNICODE
DXUtil_ConvertAnsiStringToWide( tstrDestination, strSource, cchDestChar );
#else
if( cchDestChar == -1 )
{
strcpy( tstrDestination, strSource );
}
else
{
strncpy( tstrDestination, strSource, cchDestChar );
tstrDestination[cchDestChar-1] = '\0';
}
#endif
}



Integrazione della classe CManager3D con la visualizzazione

Aggiungete alla classe CDirect3DMFCView l'oggetto CManager3D *manager3d; nella sezione pubblica, però ricordatevi di scrivere #include "Manager3D.h" prima della dichiarazione della classe CProva_GPIView.

A questo punto lanciate Class Wizard e mappatevi i seguenti eventi e funzioni virtuali di CProva_GPIView:

OnInitialUpdate
WM_ERASEBKGND
WM_SIZE


Dopo averne creato in maniera automatica le funzioni passiamo alla loro sostituzione


void CProva_GPIView::OnInitialUpdate()
{
CView::OnInitialUpdate();
CRect rect;

GetClientRect(&rect); //vengono prese le dimensioni della finestra target di rendering

manager3d = new CManager3D; //viene allocata la memoria per il manager3d

if( !manager3d->create( GetSafeHwnd(),rect.right, rect.bottom, true) )
{
MessageBox("create() - Failed", "CView"); //abilitazione effettiva della device 3d
return;
}

if( !manager3d->init() ) //inizializzazione della telecamera, della mesh X e dei render state
{
MessageBox("init() - Failed", "CView");
return;
}

if( !manager3d->resize(rect.right, rect.bottom) )
{
MessageBox("resize() - Failed", "CView");
return;
}
}


BOOL CProva_GPIView::OnEraseBkgnd(CDC* pDC)
{
return FALSE;
}

void CProva_GPIView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);

if (manager3d)
manager3d->resize(cx, cy); //cambiamento delle dimensioni della finestra target di rendering
}


Aggiungete nella costruttore della classe CProva_GPIView:

manager3d = NULL;

e nel distruttore:

if (manager3d)
delete manager3d;


Eccovi invece il codice di disegno della finestra, andate a sostituirne il codice:


void CProva_GPIView::OnDraw(CDC* pDC)
{
CProva_GPIDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

static float lastTime = (float)timeGetTime();
float currentTime = (float)timeGetTime();
float deltaTime = (currentTime - lastTime) * 0.001f; //viene calcolato il tempo che intercorre

manager3d->time = deltaTime;

//tra un frame e l?altro
if (manager3d)
{
manager3d->update(); //viene aggiornata la rotazione della mesh
manager3d->render(); //disegno della scena
}

lastTime = currentTime;
}



Includete quindi sopra i seguenti headers:


#include "mmsystem.h" //funzioni per la gestione del timer di sistema
#include "Manager3D.h" //manager 3d creato da noi


Ora andremo a modificare la classe CProva_GPIApp responsabile dell'apertura della finestra di visualizzazione:
Apriamo come al solito Class Wizard, selezionamo all'interno di class name CProva_GPIApp ed editiamoci la funzione:

OnIdle

Quindi sostituiamone il codice:


BOOL CProva_GPIApp::OnIdle(LONG lCount)
{
CWinApp::OnIdle(lCount);
AfxGetMainWnd()->Invalidate(false);

return TRUE;
}


In Questo modo verrà invalidata l'area della finestra di visualizzazione ad ogni loop dell'applicazione (ciclo idle), cioè verrà aggiornato il disegno della finestra.

Compilate il tutto e non dovreste riscontrare problemi. Da qui con l'integrazioni di finestre di dialogo modeless ed i Menù potrete implementare tool per la visualizzazione di modelli ed edito e quant?altro sfruttando le MFC per la costruzione dell'interfaccia utente! Ovviamente dovrete andare avanti con lo studio della programmazione 3D con DirectX !

Biagio Iannuzzi

Staff Tecnico
#->Vis-ingeniI<->DeveLOPmenT<-#

http://www.vis-ingenii.com ">http://www.vis-ingenii.com 
Voto medio articolo: 4.5 Numero Voti: 12
Biagio Iannuzzi

Biagio Iannuzzi

Profilo completo

Articoli collegati

Particle System
Brevi cenni su come implementare un particle system in DirectX
Autore: Stefano Cristiano | Difficoltà: | Voto:
Texture Blending (Parte II) Quake 3 Arena Shader System
Questo articolo è simile alla Parte I e ripete molti aspetti però tratta anche un pò degli shaders di Quake 3 Arena.
Autore: Stefano Cristiano | Difficoltà: | Voto:
Texture Blending (Parte I)
Come gestire gli effetti di blending tra texture. Multitexturing e Multipassing. Effect Files
Autore: Stefano Cristiano | Difficoltà: | Voto:
Sprite
Come creare uno "sprite" e animarlo. Special thanks to Elevator2 per gli sprites di megaman
Autore: Stefano Cristiano | Difficoltà: | Voto:
Texture
Introduzione al concetto di texture, di coordinate texture. Come animare le coordinate texture
Autore: Stefano Cristiano | Difficoltà:
Grafica 2D in DirectX. Vertex Buffers
Introduzione alla grafica 2D. Utilizzo dei vertex buffers
Autore: Stefano Cristiano | Difficoltà: | Voto:
Debbugging di applicazioni DirectX
Consigli e strategie per il Debugging delle applicazioni DirectX.
Autore: Stefano Cristiano | Difficoltà: | Voto:
Framework DXGWrapper
Spiega la filosofia di fondo del framework ideato dal sottoscritto ed usato da tutti i samples degli articoli relativi a DirectX.
Autore: Stefano Cristiano | Difficoltà: | Voto:
Gamma Correction e Colori
Una trattazione breve ma con una base matematica che spiega in parole povere il problema del fattore gamma e i colori in generale
Autore: Stefano Cristiano | Difficoltà: | Voto:
Inizializzazione e schermo
Si parla dell'inizializzazione dello schermo e delle modalità video in DirectX e di come gestire gli eventi "lost device", come scrivere un Game Loop e alcune info sul VSYNC
Autore: Stefano Cristiano | Difficoltà: | Commenti: 1 | Voto:
Introduzione a DirectX
Introduzione al mondo DirectX , requisiti, tools necessari, configurazione dell’ambiente di sviluppo.
Autore: Stefano Cristiano | Difficoltà: | Commenti: 2 | Voto:
Copyright © dotNetHell.it 2002-2017
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5