Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Los juegos para la Plataforma universal de Windows (UWP) 3D suelen usar polígonos para representar objetos y superficies en el juego. Las listas de vértices que componen la estructura de estos objetos y superficies poligonales se denominan mallas. Aquí, creamos una malla básica para un cubo y la proporcionamos a la canalización del sombreador para renderizar y mostrar.
Importante El código de ejemplo que se incluye aquí usa tipos (como DirectX::XMFLOAT3 y DirectX::XMFLOAT4X4) y métodos insertados declarados en DirectXMath.h. Si vas a cortar y pegar este código, #include <DirectXMath.h> en tu proyecto.
Lo que necesita saber
Tecnologías
Prerrequisitos
- Conocimientos básicos de álgebra lineal y sistemas de coordenadas 3D
- Una plantilla de Direct3D de Visual Studio 2015 o posterior
Instrucciones
Estos pasos le mostrarán cómo crear un cubo de malla básico.
Paso 1: Construir la malla para el modelo
En la mayoría de los juegos, la malla de un objeto de juego se carga desde un archivo que contiene los datos de vértices específicos. El orden de estos vértices depende de la aplicación, pero normalmente se serializan como tiras o ventiladores. Los datos de vértice pueden proceder de cualquier origen de software o se pueden crear manualmente. Depende de tu juego interpretar los datos de forma que el sombreador de vértices pueda procesarlos de forma eficaz.
En nuestro ejemplo, usamos una malla simple para un cubo. El cubo, como cualquier malla de objetos en esta fase de la canalización, se representa mediante su propio sistema de coordenadas. El sombreador de vértices toma sus coordenadas y, aplicando las matrices de transformación que proporcione, devuelve la proyección de vista 2D final en un sistema de coordenadas homogéneo.
Defina la malla de un cubo. (O cárgalo desde un archivo. ¡Es tu decisión!)
SimpleCubeVertex cubeVertices[] =
{
{ DirectX::XMFLOAT3(-0.5f, 0.5f, -0.5f), DirectX::XMFLOAT3(0.0f, 1.0f, 0.0f) }, // +Y (top face)
{ DirectX::XMFLOAT3( 0.5f, 0.5f, -0.5f), DirectX::XMFLOAT3(1.0f, 1.0f, 0.0f) },
{ DirectX::XMFLOAT3( 0.5f, 0.5f, 0.5f), DirectX::XMFLOAT3(1.0f, 1.0f, 1.0f) },
{ DirectX::XMFLOAT3(-0.5f, 0.5f, 0.5f), DirectX::XMFLOAT3(0.0f, 1.0f, 1.0f) },
{ DirectX::XMFLOAT3(-0.5f, -0.5f, 0.5f), DirectX::XMFLOAT3(0.0f, 0.0f, 1.0f) }, // -Y (bottom face)
{ DirectX::XMFLOAT3( 0.5f, -0.5f, 0.5f), DirectX::XMFLOAT3(1.0f, 0.0f, 1.0f) },
{ DirectX::XMFLOAT3( 0.5f, -0.5f, -0.5f), DirectX::XMFLOAT3(1.0f, 0.0f, 0.0f) },
{ DirectX::XMFLOAT3(-0.5f, -0.5f, -0.5f), DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f) },
};
El sistema de coordenadas del cubo coloca el centro del cubo en el origen, con el eje Y que se ejecuta de arriba abajo mediante un sistema de coordenadas izquierdo. Los valores de coordenadas se expresan como valores flotantes de 32 bits entre -1 y 1.
En cada emparejamiento entre corchetes, el segundo grupo de valores DirectX::XMFLOAT3 especifica el color asociado al vértice como un valor RGB. Por ejemplo, el primer vértice de (-0,5, 0,5, -0,5) tiene un color verde completo (el valor G se establece en 1,0 y los valores "R" y "B" se establecen en 0).
Por lo tanto, tiene 8 vértices, cada uno con un color específico. Cada emparejamiento de vértices o colores es los datos completos de un vértice en nuestro ejemplo. Al especificar nuestro búfer de vértices, debe tener en cuenta este diseño específico. Proporcionamos este diseño de entrada al sombreador de vértices para que pueda comprender los datos de vértices.
Paso 2: Configurar el diseño de entrada
Ahora, tienes los vértices en la memoria. Pero el dispositivo gráfico tiene su propia memoria y usa Direct3D para acceder a él. Para incorporar los datos de los vértices en el dispositivo gráfico para su procesamiento, tienes que dejar el camino libre, por así decirlo: debes declarar cómo están estructurados los datos de los vértices para que el dispositivo gráfico pueda interpretarlos cuando los reciba de tu juego. Para ello, use ID3D11InputLayout.
Declare y configure el diseño de entrada para el búfer de vértices.
const D3D11_INPUT_ELEMENT_DESC basicVertexLayoutDesc[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
ComPtr<ID3D11InputLayout> inputLayout;
m_d3dDevice->CreateInputLayout(
basicVertexLayoutDesc,
ARRAYSIZE(basicVertexLayoutDesc),
vertexShaderBytecode->Data,
vertexShaderBytecode->Length,
&inputLayout)
);
En este código, se especifica un diseño para los vértices, específicamente, qué datos contiene cada elemento de la lista de vértices. Aquí, en basicVertexLayoutDesc, se especifican dos componentes de datos:
POSITION: se trata de una semántica de HLSL para los datos de posición proporcionados a un sombreador. En este código, es un DirectX::XMFLOAT3, o más concretamente, una estructura con valores de punto flotante de 3 32 bits que corresponden a una coordenada 3D (x, y, z). También puede usar un float4 si proporciona la coordenada "w" homogénea y, en cuyo caso, especifique DXGI_FORMAT_R32G32B32A32_FLOAT. Ya sea que uses DirectX::XMFLOAT3 o float4 depende de las necesidades específicas de tu juego. Asegúrate de que los datos de los vértices de la malla correspondan correctamente con el formato que utilices.
Cada valor de coordenada se expresa como un valor de punto flotante entre -1 y 1, en el espacio de coordenadas del objeto. Cuando se completa el sombreador de vértices, el vértice transformado se encuentra en el espacio de proyección de vista homogéneo (con perspectiva corregida).
"¡Pero el valor de enumeración indica RGB, no XYZ!", observas con astucia. ¡Buen ojo! En ambos casos de datos de color y datos de coordenadas, normalmente se usan valores de componente 3 o 4, por lo que ¿por qué no usar el mismo formato para ambos? La semántica de HLSL, no el nombre del formato, indica cómo el sombreador trata los datos.
COLOR: se trata de una semántica de HLSL para los datos de color. Al igual que POSITION, consta de valores de punto flotante de 3 32 bits (DirectX::XMFLOAT3). Cada valor contiene un componente de color: rojo (r), azul (b) o verde (g), expresado como un número flotante entre 0 y 1.
Los valores de color normalmente se devuelven como un valor RGBA de 4 componentes al final de la tubería del sombreador. En este ejemplo, establecerá el valor alfa "A" en 1,0 (opacidad máxima) en la canalización del sombreador para todos los píxeles.
Para obtener una lista completa de los formatos, consulte DXGI_FORMAT. Para obtener una lista completa de la semántica de HLSL, consulte Semántica.
Llame a ID3D11Device::CreateInputLayout y cree el diseño de entrada en el dispositivo Direct3D. Ahora, debe crear un búfer que realmente pueda contener los datos.
Paso 3: Rellenar los búferes de vértices
Los búferes de vértices contienen la lista de vértices para cada triángulo de la malla. Cada vértice debe ser único en esta lista. En nuestro ejemplo, tiene 8 vértices para el cubo. El sombreador de vértices se ejecuta en el dispositivo gráfico y lee desde el búfer de vértices, e interpreta los datos en función del diseño de entrada especificado en el paso anterior.
En el ejemplo siguiente, se proporciona una descripción y un subrecurso para el búfer, que indican a Direct3D varias cosas sobre la asignación física de los datos de vértices y cómo tratarla en memoria en el dispositivo gráfico. Esto es necesario porque usas un ID3D11Buffer genérico , que podría contener cualquier cosa. Las estructuras D3D11_BUFFER_DESC y D3D11_SUBRESOURCE_DATA se proporcionan para asegurarse de que Direct3D comprende el diseño de memoria física del búfer, incluido el tamaño de cada elemento de vértice en el búfer, así como el tamaño máximo de la lista de vértices. También puede controlar el acceso a la memoria del búfer aquí y cómo se recorre, pero eso está un poco más allá del ámbito de este tutorial.
Después de configurar el búfer, llame a ID3D11Device::CreateBuffer para realmente crearlo. Obviamente, si tiene más de un objeto, cree búferes para cada modelo único.
Declare y cree el búfer de vértices.
D3D11_BUFFER_DESC vertexBufferDesc = {0};
vertexBufferDesc.ByteWidth = sizeof(SimpleCubeVertex) * ARRAYSIZE(cubeVertices);
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vertexBufferData;
vertexBufferData.pSysMem = cubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
ComPtr<ID3D11Buffer> vertexBuffer;
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&vertexBuffer);
Vértices cargados. ¿Pero cuál es el orden de procesamiento de estos vértices? Esto se controla cuando se proporciona una lista de índices a los vértices: la ordenación de estos índices es el orden en el que el sombreador de vértices los procesa.
Paso 4: Rellenar los búferes de índice
Ahora, se proporciona una lista de los índices de cada uno de los vértices. Estos índices corresponden a la posición del vértice en el búfer de vértices, empezando por 0. Para ayudarle a visualizar esto, tenga en cuenta que cada vértice único de la malla tiene asignado un número único, como un identificador. Este identificador es la posición entera del vértice en el búfer de vértices.
En nuestro cubo de ejemplo, tiene 8 vértices, que crean 6 quads para los lados. Divides los cuadriláteros en triángulos, para un total de 12 triángulos que usan los 8 vértices. Con 3 vértices por triángulo, tiene 36 entradas en nuestro búfer de índices. En nuestro ejemplo, este patrón de índice se conoce como una lista de triángulos y se indica a Direct3D como un D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST al establecer la topología primitiva.
Es probablemente la forma más ineficaz de enumerar índices, ya que hay muchas redundancias cuando los triángulos comparten puntos y lados. Por ejemplo, cuando un triángulo comparte un lado en una forma de rombo, se muestran 6 índices para los cuatro vértices, como este:
- Triángulo 1: [0, 1, 2]
- Triángulo 2: [0, 2, 3]
En una topología de tira o de ventilador, organizas los vértices de tal forma que elimina muchos lados redundantes durante el recorrido (como el lado desde el índice 0 hasta el índice 2 en la imagen). Para mallas grandes, esto disminuye drásticamente el número de veces que se ejecuta el sombreador de vértices y mejora significativamente el rendimiento. Sin embargo, lo mantendremos sencillo y nos quedamos con la lista de triángulos.
Declare los índices del búfer de vértices como una topología de lista de triángulos simple.
unsigned short cubeIndices[] =
{ 0, 1, 2,
0, 2, 3,
4, 5, 6,
4, 6, 7,
3, 2, 5,
3, 5, 4,
2, 1, 6,
2, 6, 5,
1, 7, 6,
1, 0, 7,
0, 3, 4,
0, 4, 7 };
¡Treinta seis elementos de índice en el búfer son muy redundantes cuando solo tiene 8 vértices! Si decide eliminar algunas de las redundancias y usar un tipo de lista de vértices diferente, como una tira o un ventilador, debe especificar ese tipo cuando proporcione un valor de D3D11_PRIMITIVE_TOPOLOGY específico al método ID3D11DeviceContext::IASetPrimitiveTopology.
Para obtener más información sobre las distintas técnicas de lista de índices, vea Topologías primitivas.
Paso 5: Crear un búfer de constantes para las matrices de transformación
Antes de que pueda empezar a procesar vértices, debe proporcionar las matrices de transformación que se aplicarán (multiplicadas) a cada vértice cuando se ejecute. Para la mayoría de los juegos 3D, hay tres de ellos:
- Matriz de 4x4 que transforma del sistema de coordenadas del objeto (modelo) hacia el sistema de coordenadas global del mundo.
- Una matriz 4x4 que convierte del sistema de coordenadas del mundo al sistema de coordenadas de la cámara (vista).
- Matriz de 4 x 4 que transforma desde el sistema de coordenadas de cámara al sistema de coordenadas de proyección de vista 2D.
Estas matrices se pasan al shader en un búfer de constantes . Un búfer de constantes es una región de memoria que permanece constante durante la ejecución del siguiente paso de la canalización del sombreador y a la que pueden acceder directamente los sombreadores desde el código HLSL. Defines cada búfer de constantes dos veces: primero en el código C++ del juego y (al menos) una vez en la sintaxis HLSL similar a C para el código del sombreador. Las dos declaraciones deben corresponderse directamente en términos de tipos y alineación de datos. Es fácil incorporar errores difíciles de encontrar cuando el sombreador utiliza la declaración HLSL para interpretar los datos declarados en C++. Esto ocurre cuando los tipos no coinciden o la alineación de los datos está desactivada.
Los búferes de constantes no son modificados por el HLSL. Puedes cambiarlos cuando el juego actualiza datos específicos. A menudo, los desarrolladores de juegos crean 4 clases de búferes de constantes: un tipo para actualizaciones por fotograma; un tipo para actualizaciones por modelo/objeto; un tipo para actualizaciones por actualización de estado del juego; y un tipo para los datos que nunca cambian durante la vigencia del juego.
En este ejemplo, solo tenemos uno que nunca cambia: los datos de DirectX::XMFLOAT4X4 para las tres matrices.
Nota El código de ejemplo que se muestra aquí usa matrices principales de columna. Puede utilizar matrices en forma de filas mediante la palabra clave row_major en HLSL, asegurándose de que los datos de su matriz de origen también estén en forma de filas. DirectXMath usa matrices por filas y se puede usar directamente con matrices HLSL definidas con la palabra clave row_major.
Declare y cree un buffer constante para las tres matrices que uses para transformar cada vértice.
struct ConstantBuffer
{
DirectX::XMFLOAT4X4 model;
DirectX::XMFLOAT4X4 view;
DirectX::XMFLOAT4X4 projection;
};
ComPtr<ID3D11Buffer> m_constantBuffer;
ConstantBuffer m_constantBufferData;
// ...
// Create a constant buffer for passing model, view, and projection matrices
// to the vertex shader. This allows us to rotate the cube and apply
// a perspective projection to it.
D3D11_BUFFER_DESC constantBufferDesc = {0};
constantBufferDesc.ByteWidth = sizeof(m_constantBufferData);
constantBufferDesc.Usage = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags = 0;
constantBufferDesc.MiscFlags = 0;
constantBufferDesc.StructureByteStride = 0;
m_d3dDevice->CreateBuffer(
&constantBufferDesc,
nullptr,
&m_constantBuffer
);
m_constantBufferData.model = DirectX::XMFLOAT4X4( // Identity matrix, since you are not animating the object
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
);
// Specify the view (camera) transform corresponding to a camera position of
// X = 0, Y = 1, Z = 2.
m_constantBufferData.view = DirectX::XMFLOAT4X4(
-1.00000000f, 0.00000000f, 0.00000000f, 0.00000000f,
0.00000000f, 0.89442718f, 0.44721359f, 0.00000000f,
0.00000000f, 0.44721359f, -0.89442718f, -2.23606800f,
0.00000000f, 0.00000000f, 0.00000000f, 1.00000000f);
Nota Normalmente, se declara la matriz de proyección al configurar recursos específicos del dispositivo, ya que los resultados de la multiplicación con él deben coincidir con los parámetros de tamaño de ventanilla 2D actuales (que a menudo corresponden al alto y ancho del píxel de la pantalla). Si esos cambian, debe escalar los valores de las coordenadas x e y en consecuencia.
// Finally, update the constant buffer perspective projection parameters
// to account for the size of the application window. In this sample,
// the parameters are fixed to a 70-degree field of view, with a depth
// range of 0.01 to 100.
float xScale = 1.42814801f;
float yScale = 1.42814801f;
if (backBufferDesc.Width > backBufferDesc.Height)
{
xScale = yScale *
static_cast<float>(backBufferDesc.Height) /
static_cast<float>(backBufferDesc.Width);
}
else
{
yScale = xScale *
static_cast<float>(backBufferDesc.Width) /
static_cast<float>(backBufferDesc.Height);
}
m_constantBufferData.projection = DirectX::XMFLOAT4X4(
xScale, 0.0f, 0.0f, 0.0f,
0.0f, yScale, 0.0f, 0.0f,
0.0f, 0.0f, -1.0f, -0.01f,
0.0f, 0.0f, -1.0f, 0.0f
);
Mientras estás aquí, establece los búferes de vértices e índices en elID3D11DeviceContext, además de la topología que estás utilizando.
// Set the vertex and index buffers, and specify the way they define geometry.
UINT stride = sizeof(SimpleCubeVertex);
UINT offset = 0;
m_d3dDeviceContext->IASetVertexBuffers(
0,
1,
vertexBuffer.GetAddressOf(),
&stride,
&offset);
m_d3dDeviceContext->IASetIndexBuffer(
indexBuffer.Get(),
DXGI_FORMAT_R16_UINT,
0);
m_d3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
¡Muy bien! Ensamblaje de la entrada completado. Todo está en su lugar para el renderizado. Vamos a poner en marcha ese sombreador de vértices.
Paso 6: Procesar la malla con el sombreador de vértices
Ahora que tiene un búfer de vértices con los vértices que definen la malla y el búfer de índices que define el orden en que se procesan los vértices, los envía al sombreador de vértices. El código del sombreador de vértices, expresado como lenguaje de sombreador de alto nivel compilado, se ejecuta una vez para cada vértice del búfer de vértices, lo que le permite realizar transformaciones por vértice. El resultado final suele ser una proyección 2D.
(¿Has cargado el sombreador de vértices? Si no es así, revisa Cómo cargar recursos en tu juego DirectX.)
Aquí, crearás el sombreador de vértices...
// Set the vertex and pixel shader stage state.
m_d3dDeviceContext->VSSetShader(
vertexShader.Get(),
nullptr,
0);
... y establece los búferes de constantes.
m_d3dDeviceContext->VSSetConstantBuffers(
0,
1,
m_constantBuffer.GetAddressOf());
Este es el código del sombreador de vértices que controla la transformación de las coordenadas del objeto a las coordenadas del mundo y, a continuación, al sistema de coordenadas de proyección de vista 2D. También aplicas una iluminación sencilla por vértice para embellecer las cosas. Esto va en el archivo HLSL del sombreador de vértices (SimplerVertexShader.hlsl, en este ejemplo).
cbuffer simpleConstantBuffer : register( b0 )
{
matrix model;
matrix view;
matrix projection;
};
struct VertexShaderInput
{
DirectX::XMFLOAT3 pos : POSITION;
DirectX::XMFLOAT3 color : COLOR;
};
struct PixelShaderInput
{
float4 pos : SV_POSITION;
float4 color : COLOR;
};
PixelShaderInput SimpleVertexShader(VertexShaderInput input)
{
PixelShaderInput vertexShaderOutput;
float4 pos = float4(input.pos, 1.0f);
// Transform the vertex position into projection space.
pos = mul(pos, model);
pos = mul(pos, view);
pos = mul(pos, projection);
vertexShaderOutput.pos = pos;
// Pass the vertex color through to the pixel shader.
vertexShaderOutput.color = float4(input.color, 1.0f);
return vertexShaderOutput;
}
¿Ves ese cbuffer en la parte superior? Ese es el análogo HLSL al mismo búfer de constantes que declaramos anteriormente en nuestro código de C++. ¿Y el VertexShaderInputstruct? ¡Vaya, parece igual a tu diseño de entrada y declaración de datos de vértices! Es importante que las declaraciones de datos de vértices y búfer de constantes en el código de C++ coincidan con las declaraciones del código HLSL y que incluya signos, tipos y alineación de datos.
PixelShaderInput especifica el diseño de los datos devueltos por la función principal del sombreador de vértices. Cuando terminas de procesar un vértice, devolverás una posición de vértice en el espacio de proyección en 2D y un color utilizado para la iluminación por vértice. La tarjeta gráfica usa la salida de datos por parte del sombreador para calcular los "fragmentos" (píxeles posibles) que se deben colorear cuando el sombreador de píxeles se ejecuta en la siguiente fase de la canalización.
Paso 7: Pasar la malla a través del sombreador de píxeles
Normalmente, en esta fase de la canalización de gráficos, se realizan operaciones por píxel en las superficies proyectadas visibles de los objetos. (A las personas les gustan las texturas). Sin embargo, para los fines de ejemplo, simplemente se pasa a través de esta fase.
En primer lugar, vamos a crear una instancia del sombreador de píxeles. El sombreador de píxeles se ejecuta para cada píxel de la proyección en 2D de la escena, asignando un color a ese píxel. En este caso, pasamos el color del píxel devuelto por el sombreador de vértices directamente.
Establezca el sombreador de píxeles.
m_d3dDeviceContext->PSSetShader( pixelShader.Get(), nullptr, 0 );
Defina un sombreador de píxeles de acceso directo en HLSL.
struct PixelShaderInput
{
float4 pos : SV_POSITION;
};
float4 SimplePixelShader(PixelShaderInput input) : SV_TARGET
{
// Draw the entire triangle yellow.
return float4(1.0f, 1.0f, 0.0f, 1.0f);
}
Coloque este código en un archivo HLSL independiente del sombreador de vértices HLSL (como SimplePixelShader.hlsl). Este código se ejecuta una vez por cada píxel visible de la ventana gráfica (una representación en memoria de la parte de la pantalla en la que está dibujando), que, en este caso, corresponde a toda la pantalla. Ahora, la canalización de gráficos está completamente definida.
Paso 8: Rasterizar y mostrar la malla
Vamos a ejecutar la canalización. Esto es fácil: llame a ID3D11DeviceContext::DrawIndexed.
¡Dibuja ese cubo!
// Draw the cube.
m_d3dDeviceContext->DrawIndexed( ARRAYSIZE(cubeIndices), 0, 0 );
Dentro de la tarjeta gráfica, cada vértice se procesa en el orden especificado en el búfer de índices. Una vez que el código haya ejecutado el sombreador de vértices y se definan los fragmentos 2D, se invoca el sombreador de píxeles y se colorea los triángulos.
Ahora, coloque el cubo en la pantalla.
Muestra ese buffer de cuadros en la pantalla.
// Present the rendered image to the window. Because the maximum frame latency is set to 1,
// the render loop is generally throttled to the screen refresh rate, typically around
// 60 Hz, by sleeping the app on Present until the screen is refreshed.
m_swapChain->Present(1, 0);
¡Y ya has terminado! Para una escena repleta de modelos, utilice varios búferes para vértices e índices, e incluso puede utilizar diferentes sombreadores para distintos tipos de modelos. Recuerde que cada modelo tiene su propio sistema de coordenadas y debe transformarlos en el sistema de coordenadas del mundo compartido mediante las matrices definidas en el búfer de constantes.
Observaciones
En este tema se describe la creación y visualización de geometría simple que se crea usted mismo. Para obtener más información sobre cómo cargar geometría más compleja desde un archivo y convertirla al formato de objeto de búfer de vértices específico del ejemplo (.vbo), consulta Cómo cargar recursos en tu juego DirectX.
Temas relacionados