¿Cómo puedo implementar un renderizador que pueda dibujar muchos tipos de primitivas?

8

Esto está relacionado de alguna manera con una pregunta que hice anteriormente sobre el dibujo de primitivas indexadas .

Mi problema era que solo dibujaba un cubo cuando quería dibujar muchos. Me dijeron que el problema era que estaba sobrescribiendo los búferes de vértices e índices con cada nueva instancia de Cubey que en su lugar debería crear uno en el origen y luego dibujar muchos, pasando a través de una matriz de transformación al sombreador que lo hace aparecer en diferentes lugares. Esto funcionó muy bien.

Sin embargo, ahora tengo un nuevo problema: ¿cómo dibujaría muchos tipos diferentes de primitivas?

Aquí está mi código de la pregunta anterior:

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));
}

Claramente, si duplicaba y modificaba el código para que fuera un objeto o forma diferente, la última forma que se inicializaría sobrescribiría el búfer de vértices, ¿no?

¿Utilizo múltiples buffers de vértices? ¿Añado el nuevo búfer de vértices al anterior y uso los índices apropiados para dibujarlos? ¿Puedo hacer cualquiera? ¿Ambos?

Sir Yakalot
fuente

Respuestas:

12

Probablemente sea una mala idea crear nuevas clases para cada tipo de geometría que va a admitir. No es muy escalable o mantenible. Además, el diseño de clase que parece querer ahora parece combinar las tareas de administrar la geometría en sí y los datos de instancia para esa geometría.

Aquí hay un enfoque que puede tomar:

Crea dos clases, Meshy MeshInstance. A Meshcontiene todas las propiedades de la geometría compartida, básicamente un búfer de vértices y un búfer de índice. Si lo desea, puede crear funciones auxiliares que creen mallas que contengan datos de vértices de cubo (o datos de vértices de esfera, o cualquier otra cosa que desee). Debe adaptar la interfaz pública de la Meshclase para permitir que dichas funciones auxiliares se implementen como funciones que no sean miembros ni amigos.

MeshInstance, por otro lado, debe construirse con referencia a a Mesh. El MeshInstancecontiene las propiedades de un objeto individual - es la transformación del mundo, y las anulaciones de sombreado utilizados para hacerlo, y así sucesivamente.

De esta manera, cuando desea crear un nuevo cubo, primero obtiene el Meshobjeto que representa un cubo de una biblioteca compartida de objetos de malla primitivos que creó al inicio. Luego creas uno nuevo MeshInstance, asignándole ese cubo Mesh.

Cuando renderiza, crea una lista de todos los MeshInstancecorreos electrónicos que desea dibujar y los envía. Si los agrupa por Mesho textura, puede optimizar la sobrecarga de cambio de estado (es decir, dibuja todas las instancias de malla correspondientes a la malla de cubo a la vez, y luego todas las instancias de malla correspondientes a la malla de esfera, por lo que tiene menos SetVertexBufferllamadas en El dispositivo D3D). También puede agrupar por otro estado, como textura y sombreado.

De esta manera, evita desperdiciar memoria duplicando datos de vértices, y se asegura de que su sistema de renderizado pueda escalar a cualquier conjunto arbitrario de primitivas simplemente implementando nuevas (a) funciones para crear mallas mediante programación o (b) funciones para cargar mallas desde archivos de Un formato particular.

Una vez que su canal de renderizado funciona en términos de Meshobjetos generalizados , es mucho más fácil adaptarlo de manera global a nuevas técnicas u optimizaciones.

Comentarios especificos:

Claramente, si duplicaba y modificaba el código para que fuera un objeto o forma diferente, la última forma que se inicializaría sobrescribiría el búfer de vértices. ¿no es así?

No. En el código que publicaste, la única forma pBuffery cosas similares se sobrescribirían si fuera una variable miembro estática. Si copió la clase mayorista para crear (por ejemplo) una Sphereclase, esa sería una nueva variable estática. Esto sigue siendo una mala idea.

¿Utilizo múltiples búferes de vértices? ¿Añado el nuevo búfer de vértices al anterior y uso los índices apropiados para dibujarlos? ¿Puedo hacer cualquiera? ¿Ambos?

La implementación ingenua de la técnica que describo anteriormente involucra múltiples buffers (uno para cada conjunto de geometría compartida). Si esa geometría es estática, es posible almacenarlo todo en uno (o múltiples, ya que existe un límite óptimo práctico para el tamaño del buffer) para minimizar aún más los cambios de estado del buffer. Eso debería considerarse una optimización y se deja como un ejercicio para el lector; primero haz que funcione, luego preocúpate por hacerlo rápido.


fuente
Sé que se supone que no debemos publicar comentarios de 'gracias', ¡pero esto es muy útil! ¡Gracias!
SirYakalot
Por el momento, pBuffer e iBuffer son externos. ¿Debo hacer que estas instancias sean miembros de cada objeto Mesh?
SirYakalot
1
Sí, ese es un buen lugar para comenzar.
Ahora que vengo a implementar esto, tengo un pequeño problema para pensar cómo hacerlo, solo la parte donde dice "Si lo desea, puede crear funciones auxiliares que creen mallas que contengan datos de vértices de cubo (o datos de vértices de esfera, o cualquier otra cosa que desee). Debería adaptar la interfaz pública de la clase Mesh para permitir que tales funciones auxiliares se implementen como funciones que no son miembros ni amigos ". ¿Qué quieres decir exactamente con funciones que no son de ayuda ni de amigos?
SirYakalot
Una función que no es miembro de la clase (por lo tanto, una función global) que tampoco se declara como amiga de la clase. Una función "regular", en otras palabras, que manipula el objeto de malla solo a través de su API pública.