Conversión del marco de representación
Resumen
- Parte 1: Inicializar Direct3D 11
- Parte 2: Conversión del marco de representación
- Parte 3: Portar el bucle del juego
Muestra cómo convertir un marco de representación simple de Direct3D 9 a Direct3D 11, incluido cómo portar búferes de geometría, cómo compilar y cargar programas de sombreador HLSL y cómo implementar la cadena de representación en Direct3D 11. Parte 2 del tutorial Portar una aplicación sencilla de Direct3D 9 a DirectX 11 y Plataforma universal de Windows (UWP).
Convertir efectos en sombreadores HLSL
El ejemplo siguiente es una técnica D3DX sencilla, escrita para la API de efectos heredada, para la transformación de vértices de hardware y los datos de color de paso a través.
Código de sombreador de Direct3D 9
// Global variables
matrix g_mWorld; // world matrix for object
matrix g_View; // view matrix
matrix g_Projection; // projection matrix
// Shader pipeline structures
struct VS_OUTPUT
{
float4 Position : POSITION; // vertex position
float4 Color : COLOR0; // vertex diffuse color
};
struct PS_OUTPUT
{
float4 RGBColor : COLOR0; // Pixel color
};
// Vertex shader
VS_OUTPUT RenderSceneVS(float3 vPos : POSITION,
float3 vColor : COLOR0)
{
VS_OUTPUT Output;
float4 pos = float4(vPos, 1.0f);
// Transform the position from object space to homogeneous projection space
pos = mul(pos, g_mWorld);
pos = mul(pos, g_View);
pos = mul(pos, g_Projection);
Output.Position = pos;
// Just pass through the color data
Output.Color = float4(vColor, 1.0f);
return Output;
}
// Pixel shader
PS_OUTPUT RenderScenePS(VS_OUTPUT In)
{
PS_OUTPUT Output;
Output.RGBColor = In.Color;
return Output;
}
// Technique
technique RenderSceneSimple
{
pass P0
{
VertexShader = compile vs_2_0 RenderSceneVS();
PixelShader = compile ps_2_0 RenderScenePS();
}
}
En Direct3D 11, todavía podemos usar nuestros sombreadores HLSL. Colocamos cada sombreador en su propio archivo HLSL para que Visual Studio los compile en archivos independientes y versiones posteriores, los cargaremos como recursos de Direct3D independientes. Establecemos el nivel de destino en Shader Model 4 Level 9_1 (/4_0_level_9_1) porque estos sombreadores se escriben para GPU directX 9.1.
Cuando definimos el diseño de entrada, nos aseguramos de que representa la misma estructura de datos que usamos para almacenar datos por vértice en la memoria del sistema y en la memoria de GPU. Del mismo modo, la salida de un sombreador de vértices debe coincidir con la estructura utilizada como entrada para el sombreador de píxeles. Las reglas no son las mismas que pasar datos de una función a otra en C++; Puede omitir variables no utilizadas al final de la estructura. Pero el orden no se puede reorganizar y no se puede omitir el contenido en medio de la estructura de datos.
Nota Las reglas de Direct3D 9 para enlazar sombreadores de vértices a sombreadores de píxeles estaban más relajados que las reglas de Direct3D 11. La disposición de Direct3D 9 era flexible, pero ineficaz.
Es posible que los archivos HLSL usen la sintaxis anterior para la semántica del sombreador; por ejemplo, COLOR en lugar de SV_TARGET. Si es así, deberá habilitar el modo de compatibilidad HLSL (opción del compilador/Gec) o actualizar la semántica del sombreador a la sintaxis actual. El sombreador de vértices de este ejemplo se ha actualizado con la sintaxis actual.
Este es nuestro sombreador de vértices de transformación de hardware, esta vez definido en su propio archivo.
Nota Los sombreadores de vértices son necesarios para generar la semántica del valor del sistema SV_POSITION. Esta semántica resuelve los datos de posición de vértice para coordinar los valores en los que x está entre -1 y 1, y está comprendido entre -1 y 1, z se divide por el valor homogéneo original w (z/w) y w es 1 dividido por el valor w original (1/w).
Sombreador de vértices HLSL (nivel de característica 9.1)
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mWorld; // world matrix for object
matrix View; // view matrix
matrix Projection; // projection matrix
};
struct VS_INPUT
{
float3 vPos : POSITION;
float3 vColor : COLOR0;
};
struct VS_OUTPUT
{
float4 Position : SV_POSITION; // Vertex shaders must output SV_POSITION
float4 Color : COLOR0;
};
VS_OUTPUT main(VS_INPUT input) // main is the default function name
{
VS_OUTPUT Output;
float4 pos = float4(input.vPos, 1.0f);
// Transform the position from object space to homogeneous projection space
pos = mul(pos, mWorld);
pos = mul(pos, View);
pos = mul(pos, Projection);
Output.Position = pos;
// Just pass through the color data
Output.Color = float4(input.vColor, 1.0f);
return Output;
}
Esto es todo lo que necesitamos para nuestro sombreador de píxeles de paso a través. Aunque lo llamamos un paso a través, en realidad se obtienen datos de color interpolados interpolados correctos para cada píxel. Tenga en cuenta que el SV_TARGET semántica del valor del sistema se aplica a la salida del valor de color por nuestro sombreador de píxeles según sea necesario para la API.
Nota El nivel del sombreador 9_x sombreadores de píxeles no puede leer de la semántica del valor del sistema de SV_POSITION. Los sombreadores de píxeles de modelo 4.0 (y posteriores) pueden usar SV_POSITION para recuperar la ubicación de píxeles en la pantalla, donde x está entre 0 y el ancho de destino de representación e y está entre 0 y el alto de destino de representación (cada desplazamiento por 0,5).
La mayoría de los sombreadores de píxeles son mucho más complejos que un paso a través; Tenga en cuenta que los niveles de características superiores de Direct3D permiten un número mucho mayor de cálculos por programa de sombreador.
Sombreador de píxeles HLSL (nivel de característica 9.1)
struct PS_INPUT
{
float4 Position : SV_POSITION; // interpolated vertex position (system value)
float4 Color : COLOR0; // interpolated diffuse color
};
struct PS_OUTPUT
{
float4 RGBColor : SV_TARGET; // pixel color (your PS computes this system value)
};
PS_OUTPUT main(PS_INPUT In)
{
PS_OUTPUT Output;
Output.RGBColor = In.Color;
return Output;
}
Compilar y cargar sombreadores
Los juegos de Direct3D 9 suelen usar la biblioteca efectos como una manera cómoda de implementar canalizaciones programables. Los efectos se pueden compilar en tiempo de ejecución mediante el método de función D3DXCreateEffectFromFile.
Carga de un efecto en Direct3D 9
// Turn off preshader optimization to keep calculations on the GPU
DWORD dwShaderFlags = D3DXSHADER_NO_PRESHADER;
// Only enable debug info when compiling for a debug target
#if defined (DEBUG) || defined (_DEBUG)
dwShaderFlags |= D3DXSHADER_DEBUG;
#endif
D3DXCreateEffectFromFile(
m_pd3dDevice,
L"CubeShaders.fx",
NULL,
NULL,
dwShaderFlags,
NULL,
&m_pEffect,
NULL
);
Direct3D 11 funciona con programas de sombreador como recursos binarios. Los sombreadores se compilan cuando se compila el proyecto y, a continuación, se tratan como recursos. Por lo tanto, nuestro ejemplo cargará el código de bytes del sombreador en la memoria del sistema, usará la interfaz del dispositivo Direct3D para crear un recurso de Direct3D para cada sombreador y apuntará a los recursos del sombreador direct3D cuando se configura cada fotograma.
Carga de un recurso de sombreador en Direct3D 11
// BasicReaderWriter is a tested file loader used in SDK samples.
BasicReaderWriter^ readerWriter = ref new BasicReaderWriter();
// Load vertex shader:
Platform::Array<byte>^ vertexShaderData =
readerWriter->ReadData("CubeVertexShader.cso");
// This call allocates a device resource, validates the vertex shader
// with the device feature level, and stores the vertex shader bits in
// graphics memory.
m_d3dDevice->CreateVertexShader(
vertexShaderData->Data,
vertexShaderData->Length,
nullptr,
&m_vertexShader
);
Para incluir el código de bytes del sombreador en el paquete de aplicación compilado, basta con agregar el archivo HLSL al proyecto de Visual Studio. Visual Studio usará la Herramienta del compilador de efectos (FXC) para compilar archivos HLSL en objetos de sombreador compilados (. Archivos de CSO) e incluyéndolas en el paquete de la aplicación.
Nota Asegúrese de establecer el nivel de característica de destino correcto para el compilador HLSL: haga clic con el botón derecho en el archivo de origen HLSL en Visual Studio, seleccione Propiedades y cambie la configuración modelo de sombreador en Compilador de HLSL -> General. Direct3D comprueba esta propiedad con respecto a las funcionalidades de hardware cuando la aplicación crea el recurso de sombreador direct3D.
Este es un buen lugar para crear el diseño de entrada, que corresponde a la declaración de flujo de vértices en Direct3D 9. La estructura de datos por vértice debe coincidir con lo que usa el sombreador de vértices; en Direct3D 11 tenemos más control sobre el diseño de entrada; podemos definir el tamaño de matriz y la longitud de bits de los vectores de punto flotante y especificar la semántica para el sombreador de vértices. Creamos una estructura de D3D11_INPUT_ELEMENT_DESC y la usamos para informar a Direct3D del aspecto que tendrán los datos por vértice. Esperamos hasta después de cargar el sombreador de vértices para definir el diseño de entrada porque la API valida el diseño de entrada en el recurso del sombreador de vértices. Si el diseño de entrada no es compatible, Direct3D produce una excepción.
Los datos por vértice deben almacenarse en tipos compatibles en la memoria del sistema. Los tipos de datos DirectXMath pueden ayudar; por ejemplo, DXGI_FORMAT_R32G32B32_FLOAT corresponde a XMFLOAT3.
Nota Los búferes de constantes usan un diseño de entrada fijo que se alinea con cuatro números de punto flotante a la vez. XMFLOAT4 (y sus derivados) se recomiendan para los datos de búfer de constantes.
Establecimiento del diseño de entrada en Direct3D 11
// Create input layout:
const D3D11_INPUT_ELEMENT_DESC vertexDesc[] =
{
{ "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 },
};
Creación de recursos de geometría
En Direct3D 9 almacenamos recursos de geometría mediante la creación de búferes en el dispositivo Direct3D, el bloqueo de la memoria y la copia de datos de la memoria de CPU a la memoria de GPU.
Direct3D 9
// Create vertex buffer:
VOID* pVertices;
// In Direct3D 9 we create the buffer, lock it, and copy the data from
// system memory to graphics memory.
m_pd3dDevice->CreateVertexBuffer(
sizeof(CubeVertices),
0,
D3DFVF_XYZ | D3DFVF_DIFFUSE,
D3DPOOL_MANAGED,
&pVertexBuffer,
NULL);
pVertexBuffer->Lock(
0,
sizeof(CubeVertices),
&pVertices,
0);
memcpy(pVertices, CubeVertices, sizeof(CubeVertices));
pVertexBuffer->Unlock();
DirectX 11 sigue un proceso más sencillo. La API copia automáticamente los datos de la memoria del sistema en la GPU. Podemos usar punteros inteligentes COM para facilitar la programación.
DirectX 11
D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
vertexBufferData.pSysMem = CubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC vertexBufferDesc(
sizeof(CubeVertices),
D3D11_BIND_VERTEX_BUFFER);
// This call allocates a device resource for the vertex buffer and copies
// in the data.
m_d3dDevice->CreateBuffer(
&vertexBufferDesc,
&vertexBufferData,
&m_vertexBuffer
);
Implementación de la cadena de representación
Los juegos de Direct3D 9 suelen usar una cadena de representación basada en efectos. Este tipo de cadena de representación configura el objeto de efecto, lo proporciona con los recursos que necesita y le permite representar cada pase.
Cadena de representación de Direct3D 9
// Clear the render target and the z-buffer.
m_pd3dDevice->Clear(
0, NULL,
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_ARGB(0, 45, 50, 170),
1.0f, 0
);
// Set the effect technique
m_pEffect->SetTechnique("RenderSceneSimple");
// Rotate the cube 1 degree per frame.
D3DXMATRIX world;
D3DXMatrixRotationY(&world, D3DXToRadian(m_frameCount++));
// Set the matrices up using traditional functions.
m_pEffect->SetMatrix("g_mWorld", &world);
m_pEffect->SetMatrix("g_View", &m_view);
m_pEffect->SetMatrix("g_Projection", &m_projection);
// Render the scene using the Effects library.
if(SUCCEEDED(m_pd3dDevice->BeginScene()))
{
// Begin rendering effect passes.
UINT passes = 0;
m_pEffect->Begin(&passes, 0);
for (UINT i = 0; i < passes; i++)
{
m_pEffect->BeginPass(i);
// Send vertex data to the pipeline.
m_pd3dDevice->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
m_pd3dDevice->SetStreamSource(
0, pVertexBuffer,
0, sizeof(VertexPositionColor)
);
m_pd3dDevice->SetIndices(pIndexBuffer);
// Draw the cube.
m_pd3dDevice->DrawIndexedPrimitive(
D3DPT_TRIANGLELIST,
0, 0, 8, 0, 12
);
m_pEffect->EndPass();
}
m_pEffect->End();
// End drawing.
m_pd3dDevice->EndScene();
}
// Present frame:
// Show the frame on the primary surface.
m_pd3dDevice->Present(NULL, NULL, NULL, NULL);
La cadena de representación de DirectX 11 seguirá realizando las mismas tareas, pero los pasos de representación deben implementarse de forma diferente. En lugar de colocar los detalles en archivos FX y permitir que las técnicas de representación sean más o menos opacas en nuestro código de C++, configuraremos toda nuestra representación en C++.
Este es el aspecto de nuestra cadena de representación. Es necesario proporcionar el diseño de entrada que creamos después de cargar el sombreador de vértices, proporcionar cada uno de los objetos de sombreador y especificar los búferes de constantes para que cada sombreador use. En este ejemplo no se incluyen varios pasos de representación, pero si se hiciera una cadena de representación similar para cada paso, cambiando la configuración según sea necesario.
Cadena de representación de Direct3D 11
// Clear the back buffer.
const float midnightBlue[] = { 0.098f, 0.098f, 0.439f, 1.000f };
m_d3dContext->ClearRenderTargetView(
m_renderTargetView.Get(),
midnightBlue
);
// Set the render target. This starts the drawing operation.
m_d3dContext->OMSetRenderTargets(
1, // number of render target views for this drawing operation.
m_renderTargetView.GetAddressOf(),
nullptr
);
// Rotate the cube 1 degree per frame.
XMStoreFloat4x4(
&m_constantBufferData.model,
XMMatrixTranspose(XMMatrixRotationY(m_frameCount++ * XM_PI / 180.f))
);
// Copy the updated constant buffer from system memory to video memory.
m_d3dContext->UpdateSubresource(
m_constantBuffer.Get(),
0, // update the 0th subresource
NULL, // use the whole destination
&m_constantBufferData,
0, // default pitch
0 // default pitch
);
// Send vertex data to the Input Assembler stage.
UINT stride = sizeof(VertexPositionColor);
UINT offset = 0;
m_d3dContext->IASetVertexBuffers(
0, // start with the first vertex buffer
1, // one vertex buffer
m_vertexBuffer.GetAddressOf(),
&stride,
&offset
);
m_d3dContext->IASetIndexBuffer(
m_indexBuffer.Get(),
DXGI_FORMAT_R16_UINT,
0 // no offset
);
m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_d3dContext->IASetInputLayout(m_inputLayout.Get());
// Set the vertex shader.
m_d3dContext->VSSetShader(
m_vertexShader.Get(),
nullptr,
0
);
// Set the vertex shader constant buffer data.
m_d3dContext->VSSetConstantBuffers(
0, // register 0
1, // one constant buffer
m_constantBuffer.GetAddressOf()
);
// Set the pixel shader.
m_d3dContext->PSSetShader(
m_pixelShader.Get(),
nullptr,
0
);
// Draw the cube.
m_d3dContext->DrawIndexed(
m_indexCount,
0, // start with index 0
0 // start with vertex 0
);
La cadena de intercambio forma parte de la infraestructura de gráficos, por lo que usamos nuestra cadena de intercambio DXGI para presentar el marco completado. DXGI bloquea la llamada hasta la siguiente vsync; luego devuelve y nuestro bucle de juego puede continuar con la siguiente iteración.
Presentación de un fotograma en la pantalla mediante DirectX 11
m_swapChain->Present(1, 0);
La cadena de representación que acabamos de crear se llamará desde un bucle de juego implementado en el método IFrameworkView::Run . Esto se muestra en la parte 3: Ventanilla y bucle del juego.