Agregar contenido visual al ejemplo Marble Maze
En este documento se describe cómo el juego Marble Maze usa Direct3D y Direct2D en el entorno de la aplicación de la Plataforma universal de Windows (UWP) para que puedas aprender los patrones y adaptarlos cuando trabajes con tu propio contenido de juego. Para saber cómo encajan los componentes visuales del juego en la estructura general de la aplicación Marble Maze, consulta Estructura de la aplicación Marble Maze.
Seguimos estos pasos básicos a medida que desarrollamos los aspectos visuales de Marble Maze:
- Cree un marco básico que inicialice los entornos direct3D y Direct2D.
- Usa programas de edición de imágenes y modelos para diseñar los recursos 2D y 3D que aparecen en el juego.
- Asegúrate de que los recursos 2D y 3D se carguen correctamente y aparezcan en el juego.
- Integre sombreadores de vértices y píxeles que mejoran la calidad visual de los recursos del juego.
- Integre la lógica del juego, como la animación y la entrada del usuario.
También nos centramos primero en agregar recursos 3D y, después, en recursos 2D. Por ejemplo, nos centramos en la lógica del juego principal antes de agregar el sistema de menús y el temporizador.
También es necesario recorrer en iteración algunos de estos pasos varias veces durante el proceso de desarrollo. Por ejemplo, al realizar cambios en los modelos de malla y mármol, también tuvimos que cambiar parte del código del sombreador que admite esos modelos.
Nota:
El código de ejemplo que corresponde a este documento se encuentra en el ejemplo de juego Marble Maze de DirectX.
Estos son algunos de los puntos clave que se describen en este documento para cuando se trabaja con DirectX y el contenido del juego visual, es decir, al inicializar las bibliotecas de gráficos de DirectX, cargar recursos de escena y actualizar y representar la escena:
- La adición de contenido del juego suele implicar muchos pasos. Estos pasos también suelen requerir iteración. Los desarrolladores de juegos suelen centrarse primero en agregar contenido del juego 3D y luego en agregar contenido 2D.
- Llegar a más clientes y darles toda una gran experiencia al admitir la mayor gama de hardware gráficos posible.
- Separe limpiamente los formatos en tiempo de diseño y en tiempo de ejecución. Estructurar los recursos en tiempo de diseño para maximizar la flexibilidad y habilitar iteraciones rápidas en el contenido. Formatee y comprima los recursos para cargar y representar lo más eficazmente posible en tiempo de ejecución.
- Creas los dispositivos Direct3D y Direct2D en una aplicación para UWP como haces en una aplicación de escritorio clásica de Windows. Una diferencia importante es la forma en que la cadena de intercambio está asociada a la ventana de salida.
- Al diseñar el juego, asegúrate de que el formato de malla que elijas admita tus escenarios clave. Por ejemplo, si tu juego requiere colisión, asegúrate de que puedes obtener datos de colisión de tus mallas.
- Separa la lógica del juego de la lógica de representación actualizando primero todos los objetos de escena antes de representarlos.
- Normalmente, dibuja los objetos de escena 3D y, a continuación, los objetos 2D que aparecen delante de la escena.
- Sincronice el dibujo con el espacio en blanco vertical para asegurarse de que el juego no dedica tiempo a dibujar fotogramas que nunca se mostrarán realmente en la pantalla. Un espacio en blanco vertical es el tiempo que pasa entre que se termina de dibujar un fotograma en el monitor y comienza el siguiente fotograma.
Introducción a los gráficos DirectX
Cuando planeamos el juego Marble Maze Universal Windows Platform (UWP), elegimos C++ y Direct3D 11.1 porque son excelentes opciones para crear juegos 3D que requieren un control máximo sobre la representación y un alto rendimiento. DirectX 11.1 admite hardware de DirectX 9 a DirectX 11 y, por tanto, puede ayudarle a llegar a más clientes de forma más eficaz porque no tiene que volver a escribir código para cada una de las versiones anteriores de DirectX.
Marble Maze usa Direct3D 11.1 para representar los activos del juego 3D, es decir, la canica y el laberinto. Marble Maze también usa Direct2D, DirectWrite y Windows Imaging Component (WIC) para dibujar los recursos del juego 2D, como los menús y el temporizador.
El desarrollo de juegos requiere planificación. Si no estás familiarizado con los gráficos de DirectX, te recomendamos que leas DirectX: Introducción a familiarizarte con los conceptos básicos de creación de un juego DirectX para UWP. Al tiempo que lees este documento y trabajas con el código fuente de Marble Maze, puedes consultar los siguientes recursos para obtener información más detallada sobre los elementos gráficos DirectX:
- Gráficos de Direct3D 11: describe Direct3D 11, una POTENTE API de gráficos 3D acelerada por hardware para representar geometría 3D en la plataforma Windows.
- Direct2D. Se describe Direct2D, una API de elementos gráficos 2D acelerada por hardware que proporciona alto rendimiento y presentación de alta calidad de geometría 2D, mapas de bits y texto.
- DirectWrite. Se describe DirectWrite, que admite la presentación de texto de alta calidad.
- Windows Imaging Component Describe WIC, una plataforma extensible que proporciona API de bajo nivel para imágenes digitales.
Niveles de características
Direct3D 11 introduce un paradigma llamado niveles de características. Un nivel de característica es un conjunto bien definido de funcionalidades de GPU. Usa los niveles de características para dirigir tu juego para ejecutarse en versiones anteriores del hardware de Direct3D. Marble Maze admite el nivel de característica 9.1 porque no requiere características avanzadas de los niveles superiores. Le recomendamos que admita la mayor gama de hardware posible y escale el contenido del juego para que los clientes que tengan equipos de gama alta o baja tengan una gran experiencia. Para obtener más información sobre los niveles de características, consulte Direct3D 11 en hardware de nivel inferior.
Inicialización de Direct3D y Direct2D
Un dispositivo representa el adaptador de pantalla. Creas los dispositivos Direct3D y Direct2D en una aplicación para UWP como haces en una aplicación de escritorio clásica de Windows. La principal diferencia es cómo conectar la cadena de intercambio de Direct3D al sistema de ventanas.
La clase DeviceResources es una base para administrar Direct3D y Direct2D. Esta clase controla la infraestructura general, no los recursos específicos del juego. Marble Maze define la clase MarbleMazeMain para controlar activos específicos del juego, que tiene una referencia a un objeto DeviceResources para darle acceso a Direct3D y Direct2D.
Durante la inicialización, el constructor DeviceResources crea recursos independientes del dispositivo y los dispositivos Direct3D y Direct2D.
// Initialize the Direct3D resources required to run.
DX::DeviceResources::DeviceResources() :
m_screenViewport(),
m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
m_d3dRenderTargetSize(),
m_outputSize(),
m_logicalSize(),
m_nativeOrientation(DisplayOrientations::None),
m_currentOrientation(DisplayOrientations::None),
m_dpi(-1.0f),
m_deviceNotify(nullptr)
{
CreateDeviceIndependentResources();
CreateDeviceResources();
}
La clase DeviceResources separa esta funcionalidad para que pueda responder más fácilmente cuando cambie el entorno. Por ejemplo, llama al método CreateWindowSizeDependentResources cuando cambia el tamaño de la ventana.
Inicialización de los generadores de Direct2D, DirectWrite y WIC
El método DeviceResources::CreateDeviceIndependentResources crea las factorías para Direct2D, DirectWrite y WIC. En los gráficos DirectX, las fábricas son los puntos de partida para crear recursos gráficos. Marble Maze especifica D2D1\_FACTORY\_TYPE\_SINGLE\_THREADED porque realiza todos los dibujos en el subproceso principal.
// These are the resources required independent of hardware.
void DX::DeviceResources::CreateDeviceIndependentResources()
{
// Initialize Direct2D resources.
D2D1_FACTORY_OPTIONS options;
ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
#if defined(_DEBUG)
// If the project is in a debug build, enable Direct2D debugging via SDK Layers.
options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
// Initialize the Direct2D Factory.
DX::ThrowIfFailed(
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory2),
&options,
&m_d2dFactory
)
);
// Initialize the DirectWrite Factory.
DX::ThrowIfFailed(
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory2),
&m_dwriteFactory
)
);
// Initialize the Windows Imaging Component (WIC) Factory.
DX::ThrowIfFailed(
CoCreateInstance(
CLSID_WICImagingFactory2,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&m_wicFactory)
)
);
}
Creación de dispositivos Direct3D y Direct2D
El método DeviceResources::CreateDeviceResources llama a D3D11CreateDevice para crear el objeto de dispositivo que representa el adaptador de pantalla direct3D. Dado que Marble Maze admite el nivel de característica 9.1 y versiones posteriores, el método DeviceResources::CreateDeviceResources especifica los niveles 9.1 a 11.1 en la matriz featureLevels. Direct3D recorre la lista en orden y proporciona a la aplicación el primer nivel de característica que está disponible. Por lo tanto, las entradas de matriz de D3D_FEATURE_LEVEL se enumeran de mayor a menor para que la aplicación obtenga el nivel de característica más alto disponible. El método DeviceResources::CreateDeviceResources obtiene el dispositivo Direct3D 11.1 consultando el dispositivo Direct3D 11 que se devuelve de D3D11CreateDevice.
// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(_DEBUG)
if (DX::SdkLayersAvailable())
{
// If the project is in a debug build, enable debugging via SDK Layers
// with this flag.
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
}
#endif
// This array defines the set of DirectX hardware feature levels this app will support.
// Note the ordering should be preserved.
// Don't forget to declare your application's minimum required feature level in its
// description. All applications are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
HRESULT hr = D3D11CreateDevice(
nullptr, // Specify nullptr to use the default adapter.
D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver.
0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
creationFlags, // Set debug and Direct2D compatibility flags.
featureLevels, // List of feature levels this app can support.
ARRAYSIZE(featureLevels), // Size of the list above.
D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for UWP apps.
&device, // Returns the Direct3D device created.
&m_d3dFeatureLevel, // Returns feature level of device created.
&context // Returns the device immediate context.
);
if (FAILED(hr))
{
// If the initialization fails, fall back to the WARP device.
// For more information on WARP, see:
// https://go.microsoft.com/fwlink/?LinkId=286690
DX::ThrowIfFailed(
D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device.
0,
creationFlags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
&device,
&m_d3dFeatureLevel,
&context
)
);
}
// Store pointers to the Direct3D 11.1 API device and immediate context.
DX::ThrowIfFailed(
device.As(&m_d3dDevice)
);
DX::ThrowIfFailed(
context.As(&m_d3dContext)
);
El método DeviceResources::CreateDeviceResources crea el dispositivo Direct2D. Direct2D usa microsoft DirectX Graphics Infrastructure (DXGI) para interoperar con Direct3D. DXGI permite compartir superficies de memoria de vídeo entre entornos de ejecución de gráficos. Marble Maze usa el dispositivo DXGI subyacente del dispositivo Direct3D para crear el dispositivo Direct2D desde la fábrica de Direct2D.
// Create the Direct2D device object and a corresponding context.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
m_d3dDevice.As(&dxgiDevice)
);
DX::ThrowIfFailed(
m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
);
DX::ThrowIfFailed(
m_d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
&m_d2dContext
)
);
Para obtener más información sobre DXGI e interoperabilidad entre Direct2D y Direct3D, consulte Introducción a DXGI e Introducción a la interoperabilidad de Direct2D y Direct3D.
Asociación de Direct3D con la vista
El método DeviceResources::CreateWindowSizeDependentResources crea los recursos gráficos que dependen de un tamaño de ventana determinado, como la cadena de intercambio y los destinos de representación de Direct3D y Direct2D. Una manera importante de que una aplicación para UWP de DirectX difiere de una aplicación de escritorio es cómo se asocia la cadena de intercambio con la ventana de salida. Una cadena de intercambio es responsable de mostrar el búfer en el que se representa el dispositivo en el monitor. La estructura de la aplicación Marble Maze describe cómo difiere el sistema de ventanas de una aplicación para UWP de una aplicación de escritorio. Dado que una aplicación para UWP no funciona con objetos HWND, Marble Maze debe usar el método IDXGIFactory2::CreateSwapChainForCoreWindow para asociar la salida del dispositivo a la vista. En el ejemplo siguiente se muestra la parte del método DeviceResources::CreateWindowSizeDependentResources que crea la cadena de intercambio.
// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
dxgiFactory->CreateSwapChainForCoreWindow(
m_d3dDevice.Get(),
reinterpret_cast<IUnknown*>(m_window.Get()),
&swapChainDesc,
nullptr,
&m_swapChain
)
);
Para minimizar el consumo de energía, que es importante hacer en dispositivos con batería, como portátiles y tabletas, el método DeviceResources::CreateWindowSizeDependentResources llama al método IDXGIDevice1::SetMaximumFrameLatency para asegurarse de que el juego se representa solo después del espacio en blanco vertical. La sincronización con el espacio en blanco vertical se describe con mayor detalle en la sección Presentar la escena de este documento.
// Ensure that DXGI does not queue more than one frame at a time. This both
// reduces latency and ensures that the application will only render after each
// VSync, minimizing power consumption.
DX::ThrowIfFailed(
dxgiDevice->SetMaximumFrameLatency(1)
);
El método DeviceResources::CreateWindowSizeDependentResources inicializa los recursos gráficos de una manera que funciona para la mayoría de los juegos.
Nota:
El término vista tiene un significado diferente en Windows Runtime que en Direct3D. En Windows Runtime, una vista hace referencia a la colección de configuraciones de interfaz de usuario para una aplicación, incluido el área de visualización y los comportamientos de entrada, además del subproceso que usa para su procesamiento. Especifique la configuración y las opciones que necesita al crear una vista. El proceso de configuración de la vista de aplicaciones se describe en Estructura de aplicaciones de Marble Maze. En Direct3D, la vista de términos tiene varios significados. Una vista de recursos define los subrecursos a los que puede acceder un recurso. Por ejemplo, cuando un objeto de textura está asociado a una vista de recursos del sombreador, ese sombreador puede acceder posteriormente a la textura. Una ventaja de una vista de recursos es que puede interpretar los datos de diferentes maneras en distintas fases de la canalización de representación. Para obtener más información sobre las vistas de recursos, consulte Vistas de recursos. Cuando se usa en el contexto de una matriz de transformación de vista o transformación de vista, la vista hace referencia a la ubicación y la orientación de la cámara. Una transformación de vista reubica los objetos en el mundo alrededor de la posición y la orientación de la cámara. Para obtener más información sobre las transformaciones de vista, vea Transformación de vista (Direct3D 9). Cómo Marble Maze usa vistas de recursos y matrices se describe con más detalle en este tema.
Carga de recursos de escena
Marble Maze usa la clase BasicLoader, que se declara en BasicLoader.h, para cargar texturas y sombreadores. Marble Maze usa la clase SDKMesh para cargar las mallas 3D para el laberinto y la canica.
Para garantizar una aplicación con capacidad de respuesta, Marble Maze carga los recursos de escena de forma asincrónica o en segundo plano. A medida que los recursos se cargan en segundo plano, el juego puede responder a eventos de ventana. Este proceso se explica con más detalle en Carga de activos del juego en segundo plano en esta guía.
Carga de la superposición 2D y la interfaz de usuario
En Marble Maze, la superposición es la imagen que aparece en la parte superior de la pantalla. La superposición siempre aparece delante de la escena. En Marble Maze, la superposición contiene el logotipo de Windows y la cadena de texto Muestra del juego Marble Maze con DirectX. La clase SampleOverlay, que se define en SampleOverlay.h, se ocupa de la administración de la superposición. Aunque usamos la superposición como parte de los ejemplos de Direct3D, puedes adaptar este código para mostrar cualquier imagen que aparezca delante de la escena.
Un aspecto importante de la superposición es que, dado que su contenido no cambia, la clase SampleOverlay dibuja o almacena en caché su contenido en un objeto ID2D1Bitmap1 durante la inicialización. En tiempo de dibujo, la clase SampleOverlay solo tiene que dibujar el mapa de bits en la pantalla. De este modo, no es necesario realizar rutinas costosas como el dibujo de texto para cada fotograma.
La interfaz de usuario (UI) consta de componentes 2D, como menús y pantallas de encabezado (HUD), que aparecen delante de la escena. Marble Maze define los siguientes elementos de la interfaz de usuario:
- Elementos de menú que permiten al usuario iniciar el juego o ver puntuaciones altas.
- Temporizador que cuenta durante tres segundos antes de que comience la reproducción.
- Temporizador que realiza un seguimiento del tiempo de reproducción transcurrido.
- Tabla que muestra los tiempos de finalización más rápidos.
- Texto que indica Pausa cuando el juego está en pausa.
Marble Maze define los elementos de la interfaz de usuario del juego en UserInterface.h. Marble Maze define la clase ElementBase como un tipo base para todos los elementos de la interfaz de usuario. La clase ElementBase define atributos como el tamaño, la posición, la alineación y la visibilidad de un elemento de interfaz de usuario. También controla cómo se actualizan y representan los elementos.
class ElementBase
{
public:
virtual void Initialize() { }
virtual void Update(float timeTotal, float timeDelta) { }
virtual void Render() { }
void SetAlignment(AlignType horizontal, AlignType vertical);
virtual void SetContainer(const D2D1_RECT_F& container);
void SetVisible(bool visible);
D2D1_RECT_F GetBounds();
bool IsVisible() const { return m_visible; }
protected:
ElementBase();
virtual void CalculateSize() { }
Alignment m_alignment;
D2D1_RECT_F m_container;
D2D1_SIZE_F m_size;
bool m_visible;
};
Al proporcionar una clase base común para los elementos de la interfaz de usuario, la clase UserInterface, que administra la interfaz de usuario, solo necesita contener una colección de objetos ElementBase, lo que simplifica la administración de la interfaz de usuario y proporciona un administrador de interfaz de usuario que es reutilizable. Marble Maze define los tipos que derivan de ElementBase que implementan comportamientos específicos del juego. Por ejemplo, HighScoreTable define el comportamiento de la tabla de puntuación alta. Para obtener más información sobre estos tipos, consulte el código fuente.
Nota:
Dado que XAML te permite crear interfaces de usuario complejas más fácilmente, como las que se encuentran en juegos de simulación y estrategia, considera si usar XAML para definir tu interfaz de usuario. Para obtener información sobre cómo desarrollar una interfaz de usuario en XAML en un juego directX para UWP, consulta Extender la muestra de juego, que hace referencia a la muestra de juego de disparos 3D de DirectX.
Carga de sombreadores
Marble Maze usa el método BasicLoader::LoadShader para cargar un sombreador desde un archivo.
Los sombreadores son la unidad fundamental de programación de GPU en los juegos de hoy en día. Casi todo el procesamiento de gráficos 3D se controla a través de sombreadores, ya sea la transformación del modelo y la iluminación de la escena, o el procesamiento de geometría más complejo, desde la máscara de caracteres hasta la teselación. Para más información sobre el modelo de programación con sombreadores, consulta HLSL.
Marble Maze usa sombreadores de vértices y píxeles. Un sombreador de vértices siempre funciona en un vértice de entrada y genera un vértice como salida. Un sombreador de píxeles toma valores numéricos, datos de textura, valores interpolados por vértice y otros datos para generar un color de píxel como salida. Dado que un sombreador transforma un elemento a la vez, el hardware gráfico que proporciona varias canalizaciones de sombreador puede procesar conjuntos de elementos en paralelo. El número de canalizaciones paralelas que están disponibles para la GPU puede ser mucho mayor que el número que está disponible para la CPU. Por lo tanto, incluso los sombreadores básicos pueden mejorar considerablemente el rendimiento.
El método MarbleMazeMain::LoadDeferredResources carga un sombreador de vértices y un sombreador de píxeles después de cargar la superposición. Las versiones en tiempo de diseño de estos sombreadores se definen en BasicVertexShader.hlsl y en BasicPixelShader.hlsl, respectivamente. Marble Maze aplica estos sombreadores tanto a la bola como al laberinto durante la fase de representación.
El proyecto Marble Maze incluye las versiones .hlsl (el formato en tiempo de diseño) y .cso (el formato en tiempo de ejecución) de los archivos de sombreador. En tiempo de compilación, Visual Studio usa el compilador de efectos fxc.exe para compilar el archivo de origen .hlsl en un sombreador binario .cso. Para obtener más información sobre la herramienta effect-compiler, vea Effect-Compiler Tool.
El sombreador de vértices usa las matrices de modelo, vista y proyección proporcionadas para transformar la geometría de entrada. Los datos de posición de la geometría de entrada se transforman y generan dos veces: una vez en el espacio de pantalla, que es necesario para la representación y, de nuevo, en el espacio mundial, para permitir que el sombreador de píxeles realice cálculos de iluminación. El vector normal de la superficie se transforma en el espacio mundial, que también usa el sombreador de píxeles para la iluminación. Las coordenadas de textura se pasan sin cambios al sombreador de píxeles.
sPSInput main(sVSInput input)
{
sPSInput output;
float4 temp = float4(input.pos, 1.0f);
temp = mul(temp, model);
output.worldPos = temp.xyz / temp.w;
temp = mul(temp, view);
temp = mul(temp, projection);
output.pos = temp;
output.tex = input.tex;
output.norm = mul(float4(input.norm, 0.0f), model).xyz;
return output;
}
El sombreador de píxeles recibe la salida del sombreador de vértices como entrada. Este sombreador realiza cálculos de iluminación para imitar un foco de borde suave que mantiene el puntero sobre el laberinto y se alinea con la posición de la canica. La iluminación es más fuerte para las superficies que apuntan directamente hacia la luz. El componente difuso se apaga a cero, ya que la superficie normal se vuelve perpendicular a la luz, y el término ambiente disminuye a medida que los puntos normales se alejan de la luz. Los puntos más cercanos a la canica (y, por lo tanto, más cerca del centro del foco) se iluminan más fuertemente. Sin embargo, la iluminación se modula para los puntos debajo de la mármol para simular una sombra suave. En un entorno real, un objeto como la canica blanca reflejaría difusamente el foco en otros objetos de la escena. Esto se aproxima a las superficies que están a la vista de la mitad brillante de la mármol. Los factores de iluminación adicionales están en ángulo relativo y distancia a la mármol. El color de píxel resultante es una composición de la textura muestreada con el resultado de los cálculos de iluminación.
float4 main(sPSInput input) : SV_TARGET
{
float3 lightDirection = float3(0, 0, -1);
float3 ambientColor = float3(0.43, 0.31, 0.24);
float3 lightColor = 1 - ambientColor;
float spotRadius = 50;
// Basic ambient (Ka) and diffuse (Kd) lighting from above.
float3 N = normalize(input.norm);
float NdotL = dot(N, lightDirection);
float Ka = saturate(NdotL + 1);
float Kd = saturate(NdotL);
// Spotlight.
float3 vec = input.worldPos - marblePosition;
float dist2D = sqrt(dot(vec.xy, vec.xy));
Kd = Kd * saturate(spotRadius / dist2D);
// Shadowing from ball.
if (input.worldPos.z > marblePosition.z)
Kd = Kd * saturate(dist2D / (marbleRadius * 1.5));
// Diffuse reflection of light off ball.
float dist3D = sqrt(dot(vec, vec));
float3 V = normalize(vec);
Kd += saturate(dot(-V, N)) * saturate(dot(V, lightDirection))
* saturate(marbleRadius / dist3D);
// Final composite.
float4 diffuseTexture = Texture.Sample(Sampler, input.tex);
float3 color = diffuseTexture.rgb * ((ambientColor * Ka) + (lightColor * Kd));
return float4(color * lightStrength, diffuseTexture.a);
}
Advertencia
El sombreador de píxeles compilado contiene 32 instrucciones aritméticas y 1 instrucción de textura. Este sombreador debe funcionar bien en equipos de escritorio o tabletas de mayor rendimiento. Sin embargo, es posible que un equipo de gama baja no pueda procesar este sombreador y aún así pueda proporcionar una velocidad de fotogramas interactiva. Tenga en cuenta el hardware típico de su público objetivo y diseñe los sombreadores para satisfacer las funcionalidades de ese hardware.
El método MarbleMazeMain::LoadDeferredResources usa el método BasicLoader::LoadShader para cargar los sombreadores. En el ejemplo siguiente se carga el sombreador de vértices. El formato en tiempo de ejecución para este sombreador es BasicVertexShader.cso. La variable de miembro m\_vertexShader es un objeto ID3D11VertexShader.
BasicLoader^ loader = ref new BasicLoader(m_deviceResources->GetD3DDevice());
D3D11_INPUT_ELEMENT_DESC layoutDesc [] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
m_vertexStride = 44; // must set this to match the size of layoutDesc above
Platform::String^ vertexShaderName = L"BasicVertexShader.cso";
loader->LoadShader(
vertexShaderName,
layoutDesc,
ARRAYSIZE(layoutDesc),
&m_vertexShader,
&m_inputLayout
);
La variable de miembro m\_inputLayout es un objeto ID3D11InputLayout. El objeto de diseño de entrada encapsula el estado de entrada de la fase del ensamblador de entrada (IA). Un trabajo de la fase de IA es hacer que los sombreadores sean más eficaces mediante el uso de valores generados por el sistema, también conocidos comosemántica, para procesar solo los primitivos o vértices que aún no se han procesado.
Use el método ID3D11Device::CreateInputLayout para crear un diseño de entrada a partir de una matriz de descripciones de elementos de entrada. La matriz contiene uno o varios elementos de entrada; cada elemento de entrada describe un elemento de datos de vértices de un búfer de vértices. El conjunto completo de descripciones de elementos de entrada describe todos los elementos de datos de vértices de todos los búferes de vértices que se enlazarán a la fase ia.
layoutDesc en el fragmento de código anterior muestra la descripción del diseño que usa Marble Maze. La descripción del diseño describe un búfer de vértices que contiene cuatro elementos de datos de vértices. Las partes importantes de cada entrada de la matriz son el nombre semántico, el formato de datos y el desplazamiento de bytes. Por ejemplo, el elemento POSITION especifica la posición del vértice en el espacio de objetos. Comienza en el desplazamiento de bytes 0 y contiene tres componentes de punto flotante (para un total de 12 bytes). El elemento NORMAL especifica el vector normal. Se inicia en el desplazamiento de bytes 12 porque aparece directamente después de POSITION en el diseño, lo que requiere 12 bytes. El elemento NORMAL contiene un entero de 32 bits sin signo de cuatro componentes.
Compare el diseño de entrada con la estructura sVSInput definida por el sombreador de vértices, como se muestra en el ejemplo siguiente. La estructura sVSInput define los elementos POSITION, NORMALy TEXCOORD0. El tiempo de ejecución de DirectX asigna cada elemento del diseño a la estructura de entrada definida por el sombreador.
struct sVSInput
{
float3 pos : POSITION;
float3 norm : NORMAL;
float2 tex : TEXCOORD0;
};
struct sPSInput
{
float4 pos : SV_POSITION;
float3 norm : NORMAL;
float2 tex : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
sPSInput main(sVSInput input)
{
sPSInput output;
float4 temp = float4(input.pos, 1.0f);
temp = mul(temp, model);
output.worldPos = temp.xyz / temp.w;
temp = mul(temp, view);
temp = mul(temp, projection);
output.pos = temp;
output.tex = input.tex;
output.norm = mul(float4(input.norm, 0.0f), model).xyz;
return output;
}
En el documento Semántica se describe cada una de las semánticas disponibles con mayor detalle.
Nota:
En un diseño, puede especificar componentes adicionales que no se usan para permitir que varios sombreadores compartan el mismo diseño. Por ejemplo, el sombreador no usa el elemento TANGENT. Puede usar el elemento TANGENT si desea experimentar con técnicas como la asignación normal. Mediante la asignación normal, también conocida como asignación de golpes, puede crear el efecto de los golpes en las superficies de los objetos. Para obtener más información sobre la asignación de golpes, consulte Asignación de golpes (Direct3D 9).
Para obtener más información sobre la fase de ensamblado de entrada, vea Fase de ensamblador de entrada e Introducción a la fasede ensamblador de entrada.
El proceso de usar los sombreadores de vértices y píxeles para representar la escena se describe en la sección Representación de la escena más adelante en este documento.
Creación del búfer de constantes
El búfer de Direct3D agrupa una colección de datos. Un búfer de constantes es un tipo de búfer que puede usar para pasar datos a sombreadores. Marble Maze usa un búfer de constantes para contener la vista del modelo (o del mundo) y las matrices de proyección para el objeto de escena activo.
En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::LoadDeferredResources crea un búfer de constantes que contendrá posteriormente los datos de la matriz. El ejemplo crea una estructura D3D11\_BUFFER\_DESC que usa la marca D3D11\_BIND\_CONSTANT\_BUFFER para especificar el uso como búfer de constantes. A continuación, en este ejemplo se pasa esa estructura al método ID3D11Device::CreateBuffer. La variable m\_constantBuffer es un objeto ID3D11Buffer.
// Create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};
// Multiple of 16 bytes
constantBufferDesc.ByteWidth = ((sizeof(ConstantBuffer) + 15) / 16) * 16;
constantBufferDesc.Usage = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags = 0;
constantBufferDesc.MiscFlags = 0;
// This will not be used as a structured buffer, so this parameter is ignored.
constantBufferDesc.StructureByteStride = 0;
DX::ThrowIfFailed(
m_deviceResources->GetD3DDevice()->CreateBuffer(
&constantBufferDesc,
nullptr, // leave the buffer uninitialized
&m_constantBuffer
)
);
El método MarbleMazeMain::Update actualiza posteriormente los objetos ConstantBuffer, uno para el laberinto y otro para la canica. El método MarbleMazeMain::Render enlaza cada objeto ConstantBuffer al búfer de constantes antes de que se represente cada objeto. En el ejemplo siguiente se muestra la estructura ConstantBuffer, que se encuentra en MarbleMazeMain.h.
// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
XMFLOAT4X4 model;
XMFLOAT4X4 view;
XMFLOAT4X4 projection;
XMFLOAT3 marblePosition;
float marbleRadius;
float lightStrength;
};
Para comprender mejor cómo se asignan los búferes de constantes al código de sombreador, compare la estructura ConstantBuffer de MarbleMazeMain.h con el búfer de constantes ConstantBuffer definido por el sombreador de vértices en BasicVertexShader.hlsl:
cbuffer ConstantBuffer : register(b0)
{
matrix model;
matrix view;
matrix projection;
float3 marblePosition;
float marbleRadius;
float lightStrength;
};
El diseño de la estructura ConstantBuffer coincide con el objeto cbuffer. La variable cbuffer especifica el registro b0, lo que significa que los datos del búfer de constantes se almacenan en el registro 0. El método MarbleMazeMain::Render especifica el registro 0 cuando activa el búfer de constantes. Este proceso se describe con más detalle más adelante en este documento.
Para obtener más información sobre los búferes de constantes, consulta Introducción a los búferes en Direct3D 11. Para obtener más información sobre la palabra clave register, consulte register.
Carga de mallas
Marble Maze usa SDK-Mesh como formato en tiempo de ejecución porque este formato proporciona una manera básica de cargar datos de malla para aplicaciones de ejemplo. Para su uso en producción, debes usar un formato de malla que cumpla los requisitos específicos del juego.
El método MarbleMazeMain::LoadDeferredResources carga los datos de malla después de cargar los sombreadores de vértices y píxeles. Una malla es una colección de datos de vértices que a menudo incluye información como posiciones, datos normales, colores, materiales y coordenadas de textura. Normalmente, las mallas se crean en el software de creación 3D y se mantienen en archivos que son independientes del código de la aplicación. La canica y el laberinto son dos ejemplos de mallas que usa el juego.
Marble Maze usa la clase SDKMesh para administrar mallas. Esta clase se declara en SDKMesh.h. SDKMesh proporciona métodos para cargar, representar y destruir datos de malla.
Importante
Marble Maze usa el formato SDK-Mesh y proporciona la clase SDKMesh solo para la ilustración. Aunque el formato SDK-Mesh es útil para el aprendizaje y para crear prototipos, es un formato muy básico que podría no cumplir los requisitos de la mayoría del desarrollo de juegos. Te recomendamos que uses un formato de malla que cumpla los requisitos específicos de tu juego.
En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::LoadDeferredResources usa el método SDKMesh::Create para cargar datos de malla para el laberinto y para la bola.
// Load the meshes.
DX::ThrowIfFailed(
m_mazeMesh.Create(
m_deviceResources->GetD3DDevice(),
L"Media\\Models\\maze1.sdkmesh",
false
)
);
DX::ThrowIfFailed(
m_marbleMesh.Create(
m_deviceResources->GetD3DDevice(),
L"Media\\Models\\marble2.sdkmesh",
false
)
);
Carga de datos de colisión
Aunque esta sección no se centra en cómo Marble Maze implementa la simulación física entre la canica y el laberinto, tenga en cuenta que la geometría de malla para el sistema físico se lee cuando se cargan las mallas.
// Extract mesh geometry for the physics system.
DX::ThrowIfFailed(
ExtractTrianglesFromMesh(
m_mazeMesh,
"Mesh_walls",
m_collision.m_wallTriList
)
);
DX::ThrowIfFailed(
ExtractTrianglesFromMesh(
m_mazeMesh,
"Mesh_Floor",
m_collision.m_groundTriList
)
);
DX::ThrowIfFailed(
ExtractTrianglesFromMesh(
m_mazeMesh,
"Mesh_floorSides",
m_collision.m_floorTriList
)
);
m_physics.SetCollision(&m_collision);
float radius = m_marbleMesh.GetMeshBoundingBoxExtents(0).x / 2;
m_physics.SetRadius(radius);
La forma en que se cargan los datos de colisión depende en gran medida del formato en tiempo de ejecución que use. Para obtener más información sobre cómo Marble Maze carga la geometría de colisión desde un archivo SDK-Mesh, vea el método MarbleMazeMain::ExtractTrianglesFromMesh en el código fuente.
Actualización del estado del juego
Marble Maze separa la lógica del juego de la lógica de representación actualizando primero todos los objetos de escena antes de representarlos.
La estructura de la aplicación Marble Maze describe el bucle principal del juego. La actualización de la escena, que forma parte del bucle del juego, se produce después de que los eventos de Windows y la entrada se procesen y antes de que se represente la escena. El método MarbleMazeMain::Update controla la actualización de la interfaz de usuario y el juego.
Actualización de la interfaz de usuario
El método MarbleMazeMain::Update llama al método UserInterface::Update para actualizar el estado de la interfaz de usuario.
UserInterface::GetInstance().Update(
static_cast<float>(m_timer.GetTotalSeconds()),
static_cast<float>(m_timer.GetElapsedSeconds()));
El método UserInterface::Update actualiza cada elemento de la colección de interfaz de usuario.
void UserInterface::Update(float timeTotal, float timeDelta)
{
for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
{
(*iter)->Update(timeTotal, timeDelta);
}
}
Las clases que derivan de ElementBase (definidas en UserInterface.h) implementan el método Update para realizar comportamientos específicos. Por ejemplo, el método StopwatchTimer::Update actualiza el tiempo transcurrido por la cantidad proporcionada y actualiza el texto que se muestra más adelante.
void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
if (m_active)
{
m_elapsedTime += timeDelta;
WCHAR buffer[16];
GetFormattedTime(buffer);
SetText(buffer);
}
TextElement::Update(timeTotal, timeDelta);
}
Actualización de la escena
El método MarbleMazeMain::Update actualiza el juego en función del estado actual de la máquina de estado (GameState, almacenado en m_gameState). Cuando el juego está en estado activo (GameState::InGameActive), Marble Maze actualiza la cámara para seguir la canica, actualiza la parte de matriz de vista de los búferes de constantes y actualiza la simulación física.
En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Update actualiza la posición de la cámara. Marble Maze usa la variable m\_resetCamera para indicar que la cámara debe restablecerse para que se posicione directamente por encima de la canica. La cámara se restablece cuando se inicia el juego o la canica cae a través del laberinto. Cuando el menú principal o la pantalla de alta puntuación está activa, la cámara se establece en una ubicación constante. De lo contrario, Marble Maze usa el parámetro timeDelta para interpolar la posición de la cámara entre sus posiciones actuales y de destino. La posición de destino está ligeramente por encima y delante de la canica. El uso del tiempo de fotograma transcurrido permite que la cámara siga gradualmente o persiga la canica.
static float eyeDistance = 200.0f;
static XMFLOAT3A eyePosition = XMFLOAT3A(0, 0, 0);
// Gradually move the camera above the marble.
XMFLOAT3A targetEyePosition;
XMStoreFloat3A(
&targetEyePosition,
XMLoadFloat3A(&marblePosition) - (XMLoadFloat3A(&g) * eyeDistance));
if (m_resetCamera)
{
eyePosition = targetEyePosition;
m_resetCamera = false;
}
else
{
XMStoreFloat3A(
&eyePosition,
XMLoadFloat3A(&eyePosition)
+ ((XMLoadFloat3A(&targetEyePosition) - XMLoadFloat3A(&eyePosition))
* min(1, static_cast<float>(m_timer.GetElapsedSeconds()) * 8)
)
);
}
// Look at the marble.
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
// Override camera position for menus.
XMStoreFloat3A(
&eyePosition,
XMLoadFloat3A(&marblePosition) + XMVectorSet(75.0f, -150.0f, -75.0f, 0.0f));
m_camera->SetViewParameters(
eyePosition,
marblePosition,
XMFLOAT3(0.0f, 0.0f, -1.0f));
}
else
{
m_camera->SetViewParameters(eyePosition, marblePosition, XMFLOAT3(0.0f, 1.0f, 0.0f));
}
En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Update actualiza los búferes de constantes para la canica y el laberinto. El modelo o el mundo del laberinto siempre permanecen en la matriz de identidades. Excepto para la diagonal principal, cuyos elementos son todos, la matriz de identidad es una matriz cuadrada compuesta de ceros. La matriz del modelo de marble se basa en sus tiempos de matriz de posición su matriz de rotación.
// Update the model matrices based on the simulation.
XMStoreFloat4x4(&m_mazeConstantBufferData.model, XMMatrixIdentity());
XMStoreFloat4x4(
&m_marbleConstantBufferData.model,
XMMatrixTranspose(
XMMatrixMultiply(
marbleRotationMatrix,
XMMatrixTranslationFromVector(XMLoadFloat3A(&marblePosition))
)
)
);
// Update the view matrix based on the camera.
XMFLOAT4X4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;
Para obtener información sobre cómo el método MarbleMazeMain::Update lee la entrada del usuario y simula el movimiento de la canica, consulte Adición de entrada e interactividad al ejemplo marble Maze.
Representación de la escena
Cuando se representa una escena, normalmente se incluyen estos pasos.
- Establezca el búfer de galería de símbolos de profundidad de destino de representación actual.
- Borre las vistas de representación y galería de símbolos.
- Prepare los sombreadores de vértices y píxeles para dibujar.
- Representar los objetos 3D en la escena.
- Represente cualquier objeto 2D que desee que aparezca delante de la escena.
- Presente la imagen representada en el monitor.
El método MarbleMazeMain::Render enlaza las vistas de destino de representación y galería de símbolos de profundidad, borra esas vistas, dibuja la escena y, a continuación, dibuja la superposición.
Preparación de los destinos de representación
Antes de representar la escena, debe establecer el búfer de galería de símbolos de profundidad de destino de representación actual. Si no se garantiza que la escena dibuje todos los píxeles de la pantalla, borre también las vistas de representación y galería de símbolos. Marble Maze borra las vistas de representación y galería de símbolos en cada fotograma para asegurarse de que no haya artefactos visibles del marco anterior.
En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Render llama al método ID3D11DeviceContext::OMSetRenderTargets para establecer el destino de representación y el búfer de galería de símbolos de profundidad como los actuales.
auto context = m_deviceResources->GetD3DDeviceContext();
// Reset the viewport to target the whole screen.
auto viewport = m_deviceResources->GetScreenViewport();
context->RSSetViewports(1, &viewport);
// Reset render targets to the screen.
ID3D11RenderTargetView *const targets[1] =
{ m_deviceResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
// Clear the back buffer and depth stencil view.
context->ClearRenderTargetView(
m_deviceResources->GetBackBufferRenderTargetView(),
DirectX::Colors::Black);
context->ClearDepthStencilView(
m_deviceResources->GetDepthStencilView(),
D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL,
1.0f,
0);
Las interfaces ID3D11RenderTargetView y ID3D11DepthStencilView admiten el mecanismo de vista de textura proporcionado por Direct3D 10 y versiones posteriores. Para obtener más información sobre las vistas de textura, vea Vistas de textura (Direct3D 10). El método OMSetRenderTargets prepara la fase de fusión de salida de la canalización de Direct3D. Para obtener más información sobre la fase de fusión de salida, consulte Fase de fusión de salida.
Preparación de los sombreadores de vértices y píxeles
Antes de representar los objetos de escena, realice los pasos siguientes para preparar los sombreadores de vértices y píxeles para dibujar:
- Establezca el diseño de entrada del sombreador como el diseño actual.
- Establezca los sombreadores de vértices y píxeles como sombreadores actuales.
- Actualice los búferes de constantes con datos que tenga que pasar a los sombreadores.
Importante
Marble Maze usa un par de sombreadores de vértices y píxeles para todos los objetos 3D. Si tu juego usa más de un par de sombreadores, debes realizar estos pasos cada vez que dibujas objetos que usan diferentes sombreadores. Para reducir la sobrecarga asociada al cambio del estado del sombreador, se recomienda agrupar llamadas de representación para todos los objetos que usan los mismos sombreadores.
En la sección Carga de sombreadores de este documento se describe cómo se crea el diseño de entrada cuando se crea el sombreador de vértices. En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Render usa el método ID3D11DeviceContext::IASetInputLayout para establecer este diseño como el diseño actual.
m_deviceResources->GetD3DDeviceContext()->IASetInputLayout(m_inputLayout.Get());
En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Render usa los métodos ID3D11DeviceContext::VSSetShader e ID3D11DeviceContext::PSSetShader para establecer los sombreadores de vértices y píxeles como sombreadores actuales, respectivamente.
// Set the vertex shader stage state.
m_deviceResources->GetD3DDeviceContext()->VSSetShader(
m_vertexShader.Get(), // use this vertex shader
nullptr, // don't use shader linkage
0); // don't use shader linkage
m_deviceResources->GetD3DDeviceContext()->PSSetShader(
m_pixelShader.Get(), // use this pixel shader
nullptr, // don't use shader linkage
0); // don't use shader linkage
m_deviceResources->GetD3DDeviceContext()->PSSetSamplers(
0, // starting at the first sampler slot
1, // set one sampler binding
m_sampler.GetAddressOf()); // to use this sampler
Una vez que MarbleMazeMain::Render establece los sombreadores y su diseño de entrada, usa el método ID3D11DeviceContext::UpdateSubresource para actualizar el búfer de constantes con las matrices de modelo, vista y proyección del laberinto. El método UpdateSubresource copia los datos de matriz de la memoria de CPU a la memoria de GPU. Recuerde que los componentes de modelo y vista de la estructura ConstantBuffer se actualizan en el método MarbleMazeMain::Update. El método MarbleMazeMain::Render llama a los métodos ID3D11DeviceContext::VSSetConstantBuffers y ID3D11DeviceContext::PSSetConstantBuffers para establecer este búfer de constantes como el actual.
// Update the constant buffer with the new data.
m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
m_constantBuffer.Get(),
0,
nullptr,
&m_mazeConstantBufferData,
0,
0);
m_deviceResources->GetD3DDeviceContext()->VSSetConstantBuffers(
0, // starting at the first constant buffer slot
1, // set one constant buffer binding
m_constantBuffer.GetAddressOf()); // to use this buffer
m_deviceResources->GetD3DDeviceContext()->PSSetConstantBuffers(
0, // starting at the first constant buffer slot
1, // set one constant buffer binding
m_constantBuffer.GetAddressOf()); // to use this buffer
El método MarbleMazeMain::Render realiza pasos similares para preparar la canica que se va a representar.
Representación del laberinto y la canica
Después de activar los sombreadores actuales, puede dibujar los objetos de escena. El método MarbleMazeMain::Render llama al método SDKMesh::Render para representar la malla del laberinto.
m_mazeMesh.Render(
m_deviceResources->GetD3DDeviceContext(),
0,
INVALID_SAMPLER_SLOT,
INVALID_SAMPLER_SLOT);
El método MarbleMazeMain::Render realiza pasos similares para representar la canica.
Como se mencionó anteriormente en este documento, la clase SDKMesh se proporciona con fines de demostración, pero no se recomienda su uso en un juego de calidad de producción. Sin embargo, observe que el método SDKMesh::RenderMesh , al que llama SDKMesh::Render, usa los métodos ID3D11DeviceContext::IASetVertexBuffers y ID3D11DeviceContext::IASetIndexBuffer para establecer los búferes de vértices e índices actuales que definen la malla y el método ID3D11DeviceContext::DrawIndexed para dibujar los búferes. Para obtener más información sobre cómo trabajar con búferes de vértices e índices, consulte Introducción a los búferes en Direct3D 11.
Dibujo de la interfaz de usuario y superposición
Después de dibujar objetos de escena 3D, Marble Maze dibuja los elementos de la interfaz de usuario 2D que aparecen delante de la escena.
El método MarbleMazeMain::Render finaliza dibujando la interfaz de usuario y la superposición.
// Draw the user interface and the overlay.
UserInterface::GetInstance().Render(m_deviceResources->GetOrientationTransform2D());
m_deviceResources->GetD3DDeviceContext()->BeginEventInt(L"Render Overlay", 0);
m_sampleOverlay->Render();
m_deviceResources->GetD3DDeviceContext()->EndEvent();
El método UserInterface::Render usa un objeto ID2D1DeviceContext para dibujar los elementos de la interfaz de usuario. Este método establece el estado de dibujo, dibuja todos los elementos activos de la interfaz de usuario y, a continuación, restaura el estado de dibujo anterior.
void UserInterface::Render(D2D1::Matrix3x2F orientation2D)
{
m_d2dContext->SaveDrawingState(m_stateBlock.Get());
m_d2dContext->BeginDraw();
m_d2dContext->SetTransform(orientation2D);
m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
{
if ((*iter)->IsVisible())
(*iter)->Render();
}
// We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
// is lost. It will be handled during the next call to Present.
HRESULT hr = m_d2dContext->EndDraw();
if (hr != D2DERR_RECREATE_TARGET)
{
DX::ThrowIfFailed(hr);
}
m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}
El método SampleOverlay::Render usa una técnica similar para dibujar el mapa de bits de superposición.
Presentación de la escena
Después de dibujar todos los objetos de escena 2D y 3D, Marble Maze presenta la imagen representada al monitor. Sincroniza el dibujo con el espacio en blanco vertical para asegurarse de que el tiempo no se dedica a dibujar fotogramas que nunca se mostrarán realmente en la pantalla. Marble Maze también controla los cambios del dispositivo cuando presenta la escena.
Una vez devuelto el método MarbleMazeMain::Render, el bucle del juego llama al método DX::DeviceResources::Present para enviar la imagen representada al monitor o la pantalla. El método DX::DeviceResources::Present llama a IDXGISwapChain::Present para realizar la operación actual, como se muestra en el ejemplo siguiente:
// The first argument instructs DXGI to block until VSync, putting the application
// to sleep until the next VSync. This ensures we don't waste any cycles rendering
// frames that will never be displayed to the screen.
HRESULT hr = m_swapChain->Present(1, 0);
En este ejemplo, m\_swapChain es un objeto IDXGISwapChain1. La inicialización de este objeto se describe en la sección Inicialización de Direct3D y Direct2D en este documento.
El primer parámetro de IDXGISwapChain::Present, SyncInterval, especifica el número de espacios en blanco verticales que se deben esperar antes de presentar el marco. Marble Maze especifica 1 para que espere hasta el siguiente espacio en blanco vertical.
El método IDXGISwapChain::Present devuelve un código de error que indica que el dispositivo se quitó o, de lo contrario, produjo un error. En este caso, Marble Maze reinicializa el dispositivo.
// If the device was removed either by a disconnection or a driver upgrade, we
// must recreate all device resources.
if (hr == DXGI_ERROR_DEVICE_REMOVED)
{
HandleDeviceLost();
}
else
{
DX::ThrowIfFailed(hr);
}
Pasos siguientes
Consulta Agregar métodos de entrada e interactividad en la muestra de Marble Maze para obtener información sobre algunos de los procedimientos clave a tener en cuenta cuando trabajes con dispositivos de entrada. En este documento se describe cómo Marble Maze admite la entrada mediante función táctil, acelerómetro, controlador de Xbox 360 o mouse.
Temas relacionados
- Agregar controles de entrada e interactividad al ejemplo Marble Maze
- Estructura de la aplicación Marble Maze
- Desarrollar Marble Maze, un juego para UWP en C++ y DirectX