Come posso implementare un renderer in grado di disegnare molti tipi di primitive?


8

Ciò è in qualche modo collegato a una domanda che avevo precedentemente posto in merito al disegno di primitive indicizzate .

Il mio problema era che stavo disegnando un cubo solo quando volevo disegnarne molti. Mi è stato detto che il problema era che sovrascrivevo i buffer dei vertici e degli indici con ogni nuova istanza di Cubee che invece dovevo crearne uno all'origine e quindi disegnarne molti, passando attraverso una matrice di trasformazione allo shader che lo fa apparire in diversi posti. Questo ha funzionato magnificamente.

Ora ho un nuovo problema: come disegnerei molti diversi tipi di primitivi?

Ecco il mio codice dalla domanda precedente:

Cube::Cube(D3DXCOLOR colour, D3DXVECTOR3 min, D3DXVECTOR3 max)
{
// create eight vertices to represent the corners of the cube
VERTEX OurVertices[] =
{
    {D3DXVECTOR3(min.x, max.y, max.z), colour},
    {D3DXVECTOR3(min.x, max.y, min.z), colour},
    {D3DXVECTOR3(min.x, min.y, max.z), colour},
    {min, colour},
    {max, colour},
    {D3DXVECTOR3(max.x, max.y, min.z), colour},
    {D3DXVECTOR3(max.x, min.y, max.z), colour},
    {D3DXVECTOR3(max.x, min.y, min.z), colour},
};

// create the vertex buffer
D3D10_BUFFER_DESC bd;
bd.Usage = D3D10_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(VERTEX) * 8;
bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;

device->CreateBuffer(&bd, NULL, &pBuffer);

void* pVoid;    // the void pointer

pBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the vertex buffer
memcpy(pVoid, OurVertices, sizeof(OurVertices));    // copy the vertices to the buffer
pBuffer->Unmap();

// create the index buffer out of DWORDs
DWORD OurIndices[] = 
{
    0, 1, 2,    // side 1
    2, 1, 3,
    4, 0, 6,    // side 2
    6, 0, 2,
    7, 5, 6,    // side 3
    6, 5, 4,
    3, 1, 7,    // side 4
    7, 1, 5,
    4, 5, 0,    // side 5
    0, 5, 1,
    3, 7, 2,    // side 6
    2, 7, 6,
};

// create the index buffer
// D3D10_BUFFER_DESC bd;    // redefinition
bd.Usage = D3D10_USAGE_DYNAMIC;
bd.ByteWidth = sizeof(DWORD) * 36;
bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
bd.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
bd.MiscFlags = 0;

device->CreateBuffer(&bd, NULL, &iBuffer);

iBuffer->Map(D3D10_MAP_WRITE_DISCARD, 0, &pVoid);    // map the index buffer
memcpy(pVoid, OurIndices, sizeof(OurIndices));    // copy the indices to the buffer
iBuffer->Unmap();

//this is simply a single call to the update method that sets up the scale, rotation
//and translation matrices, in case the cubes are static and you don't want to have to 
//call update every frame
Update(D3DXVECTOR3(1, 1, 1), D3DXVECTOR3(0, 0, 0), D3DXVECTOR3(0, 0, 0));
}

Chiaramente se duplicassi e modificassi il codice in un oggetto o una forma diversi, l'ultima forma da inizializzare sovrascriverebbe il buffer dei vertici, no?

Uso più buffer di vertici? Aggiungo il nuovo buffer dei vertici a quello vecchio e utilizzo gli indici appropriati per disegnarli? Posso fare una delle due? Tutti e due?

Risposte:


12

Probabilmente è una cattiva idea creare nuove classi per ogni tipo di geometria che si intende supportare. Non è molto scalabile o mantenibile. Inoltre, il design della classe che sembra desiderare ora sembra confondere le attività di gestione della geometria stessa e dei dati di istanza per quella geometria.

Ecco un approccio che puoi adottare:

Creare due classi Meshe MeshInstance. A Meshcontiene tutte le proprietà della geometria condivisa, sostanzialmente un buffer di vertici e un buffer di indice. Se lo desideri, puoi creare funzioni di supporto che creano mesh contenenti i dati del vertice del cubo (o dati del vertice della sfera o qualsiasi altra cosa ti piaccia). È necessario personalizzare l'interfaccia pubblica della Meshclasse per consentire l'implementazione di tali funzioni di supporto come funzioni non membro e non amico.

MeshInstanced'altra parte, deve essere costruito con un riferimento a Mesh. Il MeshInstancecontiene le proprietà di un singolo oggetto - è la trasformazione del mondo, e le sostituzioni di shader utilizzati per renderla, e così via.

In questo modo, quando si desidera creare un nuovo cubo, si ottiene innanzitutto l' Meshoggetto che rappresenta un cubo da una libreria condivisa di oggetti mesh primitivi creati all'avvio. Quindi ne crei uno nuovo MeshInstanceassegnandogli quel cubo Mesh.

Quando MeshInstanceesegui il rendering, crei un elenco di tutti i messaggi che desideri disegnare e li invii. Se li raggruppi per Mesho trama, puoi ottimizzare l'overhead del cambio di stato (ovvero, disegna tutte le istanze mesh corrispondenti alla mesh del cubo in una volta, quindi tutte le istanze mesh corrispondenti alla mesh della sfera, quindi hai meno SetVertexBufferchiamate su il dispositivo D3D). Puoi anche raggruppare per altro stato, come texture e shader.

In questo modo, si evita di sprecare memoria duplicando i dati del vertice e si assicura che il proprio sistema di rendering possa adattarsi a qualsiasi set arbitrario di primitive semplicemente implementando nuove funzioni (a) per creare mesh programmaticamente o (b) per caricare mesh da file di un formato particolare.

Una volta che la pipeline di rendering funziona in termini di Meshoggetti generalizzati , è molto più facile adattarla in modo globale a nuove tecniche o ottimizzazioni.

Commenti specifici:

Chiaramente se duplicassi e modificassi il codice in modo che fosse un oggetto o una forma diversi, l'ultima forma da inizializzare sovrascriverebbe il buffer dei vertici. no?

No. Nel codice che hai pubblicato, l'unico modo pBuffere simili sarebbero stati sovrascritti se fosse una variabile membro statica. Se hai copiato la classe all'ingrosso per creare (ad esempio) una Sphereclasse, quella sarebbe una nuova variabile statica. Questa è ancora una cattiva idea.

Uso più buffer di vertici? Aggiungo il nuovo buffer dei vertici a quello vecchio e utilizzo gli indici appropriati per disegnarli? Posso fare una delle due? Tutti e due?

L'implementazione ingenua della tecnica che descrivo sopra coinvolge più buffer (uno per ogni set di geometria condivisa). Se quella geometria è statica, è possibile memorizzarla tutta in uno (o multiplo, in quanto esiste un limite ottimale pratico alla dimensione del buffer) per minimizzare ulteriormente le modifiche allo stato del buffer. Questo dovrebbe essere considerato un'ottimizzazione e viene lasciato come esercizio per il lettore; prima farlo funzionare, quindi preoccuparsi di farlo velocemente.


So che non dovremmo pubblicare commenti di ringraziamento, ma è molto utile! Grazie!
SirYakalot,

al momento pBuffer e iBuffer sono esterni. Devo rendere questi membri di istanza di ciascun oggetto Mesh?
SirYakalot,

1
Sì, è un buon punto di partenza.

Ora che vengo a implementare questo, ho un po 'di problemi a pensare a come farlo, proprio la parte in cui dici "Se vuoi, puoi creare funzioni di supporto che creano mesh contenenti i dati del vertice del cubo (o dati del vertice della sfera, o qualsiasi altra cosa ti piaccia). Dovresti personalizzare l'interfaccia pubblica della classe Mesh per consentire a tali funzioni di supporto di essere implementate come funzioni non membro e non amico. " cosa intendi esattamente con funzioni non di aiuto, non di amicizia?
SirYakalot,

Una funzione che non è un membro della classe (quindi una funzione globale) che non viene dichiarata come amica della classe. Una funzione "normale", in altre parole, che manipola l'oggetto mesh solo attraverso la sua API pubblica.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.