Marco de representación I: Introducción a la representación
Nota:
Este tema forma parte de la serie de tutoriales Crear un juego sencillo para la Plataforma universal de Windows (UWP) con DirectX. El tema de ese vínculo establece el contexto de la serie.
Hasta ahora hemos tratado cómo estructurar un juego de Plataforma universal de Windows (UWP) y cómo definir una máquina de estado para controlar el flujo del juego. Ahora es el momento de aprender a desarrollar el marco de representación. Veamos cómo el juego de ejemplo representa la escena del juego mediante Direct3D 11.
Direct3D 11 contiene un conjunto de API que proporcionan acceso a las características avanzadas del hardware gráfico de alto rendimiento que se puede usar para crear gráficos 3D para aplicaciones que consumen muchos gráficos, como juegos.
Representar gráficos de juego en pantalla significa básicamente representar una secuencia de fotogramas en pantalla. En cada fotograma, debe representar objetos visibles en la escena, en función de la vista.
Para representar un fotograma, debe pasar la información de escena necesaria al hardware para que se pueda mostrar en la pantalla. Si quieres que se muestre algo en pantalla, debes empezar a representarlo en cuanto el juego empiece a ejecutarse.
Objetivos
Para configurar un marco de representación básico para mostrar la salida de gráficos de un juego DirectX para UWP. Puede dividirlo de forma flexible en estos tres pasos.
- Establezca una conexión con la interfaz gráfica.
- Cree los recursos necesarios para dibujar los gráficos.
- Muestre los gráficos mediante la representación del marco.
En este tema se explica cómo se representan los gráficos, que abarcan los pasos 1 y 3.
Marco de representación II: La representación de juegos cubre el paso 2: cómo configurar el marco de representación y cómo se preparan los datos antes de que se pueda producir la representación.
Introducción
Es una buena idea familiarizarse con los conceptos básicos de gráficos y representación. Si no está familiarizado con Direct3D y la representación, consulte Términos y conceptos para obtener una breve descripción de los gráficos y los términos de representación usados en este tema.
Para este juego, la clase GameRenderer representa el representador de este juego de ejemplo. Es responsable de crear y mantener todos los objetos Direct3D 11 y Direct2D usados para generar los objetos visuales del juego. También mantiene una referencia al objeto Simple3DGame que se usa para recuperar la lista de objetos que se van a representar, así como el estado del juego para la pantalla de la pantalla de la cabeza (HUD).
En esta parte del tutorial, nos centraremos en representar objetos 3D en el juego.
Establecimiento de una conexión a la interfaz gráfica
Para obtener información sobre cómo acceder al hardware para la representación, consulta el tema Definir el marco de la aplicación para UWP del juego.
El método App::Initialize
La función std::make_shared , como se muestra a continuación, se usa para crear un shared_ptr a DX::D eviceResources, que también proporciona acceso al dispositivo.
En Direct3D 11, se usa un dispositivo para asignar y destruir objetos, representar primitivos y comunicarse con la tarjeta gráfica a través del controlador de gráficos.
void Initialize(CoreApplicationView const& applicationView)
{
...
// At this point we have access to the device.
// We can create the device-dependent resources.
m_deviceResources = std::make_shared<DX::DeviceResources>();
}
Mostrar los gráficos mediante la representación del marco
La escena del juego debe representarse cuando se inicia el juego. Las instrucciones para representar se inician en el método GameMain::Run , como se muestra a continuación.
El flujo simple es esto.
- Actualizar
- Representar
- Presente
GameMain::Run (método)
void GameMain::Run()
{
while (!m_windowClosed)
{
if (m_visible) // if the window is visible
{
switch (m_updateState)
{
...
default:
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
Update();
m_renderer->Render();
m_deviceResources->Present();
m_renderNeeded = false;
}
}
else
{
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
m_game->OnSuspending(); // Exiting due to window close, so save state.
}
Actualizar
Consulta el tema Administración del flujo de juegos para obtener más información sobre cómo se actualizan los estados del juego en el método GameMain::Update.
Renderización
La representación se implementa llamando al método GameRenderer::Render desde GameMain::Run.
Si la representación estéreo está habilitada, hay dos pases de representación: uno para el ojo izquierdo y otro para la derecha. En cada paso de representación, enlazamos el destino de representación y la vista de galería de símbolos de profundidad al dispositivo. También borramos la vista de galería de símbolos de profundidad después.
Nota:
La representación estéreo se puede lograr mediante otros métodos, como el estéreo de paso único, mediante la creación de instancias de vértices o sombreadores de geometría. El método de pasos de representación en dos es una manera más lenta pero más cómoda de lograr la representación estéreo.
Una vez que el juego se está ejecutando y los recursos se cargan, actualizamos la matriz de proyección, una vez por pase de representación. Los objetos son ligeramente diferentes de cada vista. A continuación, configuramos la canalización de representación de gráficos.
Nota:
Consulte Creación y carga de recursos gráficos de DirectX para obtener más información sobre cómo se cargan los recursos.
En este juego de ejemplo, el representador está diseñado para usar un diseño de vértice estándar en todos los objetos. Esto simplifica el diseño del sombreador y permite cambios sencillos entre sombreadores, independientemente de la geometría de los objetos.
Método GameRenderer::Render
Establecemos el contexto de Direct3D para usar un diseño de vértice de entrada. Los objetos de diseño de entrada describen cómo se transmiten los datos del búfer de vértices a la canalización de representación.
A continuación, establecemos el contexto de Direct3D para usar los búferes de constantes definidos anteriormente, que se usan en la fase de canalización del sombreador de vértices y la fase de canalización del sombreador de píxeles.
Nota:
Consulta Rendering framework II: Representación de juegos para obtener más información sobre la definición de los búferes de constantes.
Dado que se usa el mismo diseño de entrada y conjunto de búferes de constantes para todos los sombreadores que se encuentran en la canalización, se configura una vez por fotograma.
void GameRenderer::Render()
{
bool stereoEnabled{ m_deviceResources->GetStereoState() };
auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };
int renderingPasses = 1;
if (stereoEnabled)
{
renderingPasses = 2;
}
for (int i = 0; i < renderingPasses; i++)
{
// Iterate through the number of rendering passes to be completed.
// 2 rendering passes if stereo is enabled.
if (i > 0)
{
// Doing the Right Eye View.
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetViewRight() };
// Resets render targets to the screen.
// OMSetRenderTargets binds 2 things to the device.
// 1. Binds one render target atomically to the device.
// 2. Binds the depth-stencil view, as returned by the GetDepthStencilView method, to the device.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargets
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
// Clears the depth stencil view.
// A depth stencil view contains the format and buffer to hold depth and stencil info.
// For more info about depth stencil view, go to:
// https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-stencil-view--dsv-
// A depth buffer is used to store depth information to control which areas of
// polygons are rendered rather than hidden from view. To learn more about a depth buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-buffers
// A stencil buffer is used to mask pixels in an image, to produce special effects.
// The mask determines whether a pixel is drawn or not,
// by setting the bit to a 1 or 0. To learn more about a stencil buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/stencil-buffers
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// Direct2D -- discussed later
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmapRight());
}
else
{
// Doing the Mono or Left Eye View.
// As compared to the right eye:
// m_deviceResources->GetBackBufferRenderTargetView instead of GetBackBufferRenderTargetViewRight
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };
// Same as the Right Eye View.
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// d2d -- Discussed later under Adding UI
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmap());
}
const float clearColor[4] = { 0.5f, 0.5f, 0.8f, 1.0f };
// Only need to clear the background when not rendering the full 3D scene since
// the 3D world is a fully enclosed box and the dynamics prevents the camera from
// moving outside this space.
if (i > 0)
{
// Doing the Right Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetViewRight(), clearColor);
}
else
{
// Doing the Mono or Left Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), clearColor);
}
// Render the scene objects
if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
{
// This section is only used after the game state has been initialized and all device
// resources needed for the game have been created and associated with the game objects.
if (stereoEnabled)
{
// When doing stereo, it is necessary to update the projection matrix once per rendering pass.
auto orientation = m_deviceResources->GetOrientationTransform3D();
ConstantBufferChangeOnResize changesOnResize;
// Apply either a left or right eye projection, which is an offset from the middle
XMStoreFloat4x4(
&changesOnResize.projection,
XMMatrixMultiply(
XMMatrixTranspose(
i == 0 ?
m_game->GameCamera().LeftEyeProjection() :
m_game->GameCamera().RightEyeProjection()
),
XMMatrixTranspose(XMLoadFloat4x4(&orientation))
)
);
d3dContext->UpdateSubresource(
m_constantBufferChangeOnResize.get(),
0,
nullptr,
&changesOnResize,
0,
0
);
}
// Update variables that change once per frame.
ConstantBufferChangesEveryFrame constantBufferChangesEveryFrameValue;
XMStoreFloat4x4(
&constantBufferChangesEveryFrameValue.view,
XMMatrixTranspose(m_game->GameCamera().View())
);
d3dContext->UpdateSubresource(
m_constantBufferChangesEveryFrame.get(),
0,
nullptr,
&constantBufferChangesEveryFrameValue,
0,
0
);
// Set up the graphics pipeline. This sample uses the same InputLayout and set of
// constant buffers for all shaders, so they only need to be set once per frame.
// For more info about the graphics or rendering pipeline, see
// https://learn.microsoft.com/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline
// IASetInputLayout binds an input-layout object to the input-assembler (IA) stage.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
// Set up the Direct3D context to use this vertex layout. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetinputlayout
d3dContext->IASetInputLayout(m_vertexLayout.get());
// VSSetConstantBuffers sets the constant buffers used by the vertex shader pipeline stage.
// Set up the Direct3D context to use these constant buffers. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-vssetconstantbuffers
ID3D11Buffer* constantBufferNeverChanges{ m_constantBufferNeverChanges.get() };
d3dContext->VSSetConstantBuffers(0, 1, &constantBufferNeverChanges);
ID3D11Buffer* constantBufferChangeOnResize{ m_constantBufferChangeOnResize.get() };
d3dContext->VSSetConstantBuffers(1, 1, &constantBufferChangeOnResize);
ID3D11Buffer* constantBufferChangesEveryFrame{ m_constantBufferChangesEveryFrame.get() };
d3dContext->VSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
ID3D11Buffer* constantBufferChangesEveryPrim{ m_constantBufferChangesEveryPrim.get() };
d3dContext->VSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
// Sets the constant buffers used by the pixel shader pipeline stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-pssetconstantbuffers
d3dContext->PSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
d3dContext->PSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
ID3D11SamplerState* samplerLinear{ m_samplerLinear.get() };
d3dContext->PSSetSamplers(0, 1, &samplerLinear);
for (auto&& object : m_game->RenderObjects())
{
// The 3D object render method handles the rendering.
// For more info, see Primitive rendering below.
object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
}
}
// Start of 2D rendering
...
}
}
Representación primitiva
Al representar la escena, recorre en bucle todos los objetos que deben representarse. Los pasos siguientes se repiten para cada objeto (primitivo).
- Actualice el búfer de constantes (m_constantBufferChangesEveryPrim) con la matriz de transformación mundial del modelo e información de material.
- El m_constantBufferChangesEveryPrim contiene parámetros para cada objeto. Incluye la matriz de transformación de objetos a mundo, así como propiedades de material como el exponente de color y especular para cálculos de iluminación.
- Establezca el contexto de Direct3D para usar el diseño del vértice de entrada para que los datos del objeto de malla se transmitan a la fase del ensamblador de entrada (IA) de la canalización de representación.
- Establezca el contexto de Direct3D para usar un búfer de índice en la fase de IA. Proporcione la información primitiva: tipo, orden de datos.
- Envíe una llamada de dibujo para dibujar el primitivo indizado y no con instancias. El método GameObject::Render actualiza el búfer de constantes primitivos con los datos específicos de un primitivo determinado. Esto da como resultado una llamada a DrawIndexed en el contexto para dibujar la geometría de ese primitivo. En concreto, esta llamada dibuja comandos y datos a la unidad de procesamiento de gráficos (GPU), como parametrizado por los datos del búfer de constantes. Cada llamada de dibujo ejecuta el sombreador de vértices una vez por vértice y, a continuación, el sombreador de píxeles una vez por cada píxel de cada triángulo del primitivo. Las texturas forman parte del estado que usa el sombreador de píxeles para realizar la representación.
Estas son las razones para usar varios búferes de constantes.
- El juego usa varios búferes de constantes, pero solo necesita actualizar estos búferes una vez por primitivo. Como se mencionó anteriormente, los búferes de constantes son como entradas para los sombreadores que se ejecutan para cada primitivo. Algunos datos son estáticos (m_constantBufferNeverChanges); algunos datos son constantes sobre el marco (m_constantBufferChangesEveryFrame), como la posición de la cámara; y algunos datos son específicos del primitivo, como su color y texturas (m_constantBufferChangesEveryPrim).
- El representador del juego separa estas entradas en diferentes búferes de constantes para optimizar el ancho de banda de memoria que usa la CPU y la GPU. Este enfoque también ayuda a minimizar la cantidad de datos que la GPU necesita para realizar un seguimiento. La GPU tiene una gran cola de comandos y cada vez que el juego llama a Draw, ese comando se pone en cola junto con los datos asociados a él. Cuando el juego actualiza el búfer de constantes primitivo y emite el siguiente comando Draw , el controlador de gráficos agrega este siguiente comando y los datos asociados a la cola. Si el juego dibuja 100 primitivos, podría tener 100 copias de los datos de búfer de constantes en la cola. Para minimizar la cantidad de datos que el juego envía a la GPU, el juego usa un búfer de constantes primitivos independiente que solo contiene las actualizaciones de cada primitivo.
Método GameObject::Render
void GameObject::Render(
_In_ ID3D11DeviceContext* context,
_In_ ID3D11Buffer* primitiveConstantBuffer
)
{
if (!m_active || (m_mesh == nullptr) || (m_normalMaterial == nullptr))
{
return;
}
ConstantBufferChangesEveryPrim constantBuffer;
// Put the model matrix info into a constant buffer, in world matrix.
XMStoreFloat4x4(
&constantBuffer.worldMatrix,
XMMatrixTranspose(ModelMatrix())
);
// Check to see which material to use on the object.
// If a collision (a hit) is detected, GameObject::Render checks the current context, which
// indicates whether the target has been hit by an ammo sphere. If the target has been hit,
// this method applies a hit material, which reverses the colors of the rings of the target to
// indicate a successful hit to the player. Otherwise, it applies the default material
// with the same method. In both cases, it sets the material by calling Material::RenderSetup,
// which sets the appropriate constants into the constant buffer. Then, it calls
// ID3D11DeviceContext::PSSetShaderResources to set the corresponding texture resource for the
// pixel shader, and ID3D11DeviceContext::VSSetShader and ID3D11DeviceContext::PSSetShader
// to set the vertex shader and pixel shader objects themselves, respectively.
if (m_hit && m_hitMaterial != nullptr)
{
m_hitMaterial->RenderSetup(context, &constantBuffer);
}
else
{
m_normalMaterial->RenderSetup(context, &constantBuffer);
}
// Update the primitive constant buffer with the object model's info.
context->UpdateSubresource(primitiveConstantBuffer, 0, nullptr, &constantBuffer, 0, 0);
// Render the mesh.
// See MeshObject::Render method below.
m_mesh->Render(context);
}
Método MeshObject::Render
void MeshObject::Render(_In_ ID3D11DeviceContext* context)
{
// PNTVertex is a struct. stride provides us the size required for all the mesh data
// struct PNTVertex
//{
// DirectX::XMFLOAT3 position;
// DirectX::XMFLOAT3 normal;
// DirectX::XMFLOAT2 textureCoordinate;
//};
uint32_t stride{ sizeof(PNTVertex) };
uint32_t offset{ 0 };
// Similar to the main render loop.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
ID3D11Buffer* vertexBuffer{ m_vertexBuffer.get() };
context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
// IASetIndexBuffer binds an index buffer to the input-assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetindexbuffer.
context->IASetIndexBuffer(m_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);
// Binds information about the primitive type, and data order that describes input data for the input assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetprimitivetopology.
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Draw indexed, non-instanced primitives. A draw API submits work to the rendering pipeline.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexed.
context->DrawIndexed(m_indexCount, 0, 0);
}
Método DeviceResources::P resent
Llamamos al método DeviceResources::P resent para mostrar el contenido que hemos colocado en los búferes.
Usamos la cadena de intercambio de términos para una colección de búferes que se usan para mostrar fotogramas al usuario. Cada vez que una aplicación presenta un nuevo marco para mostrar, el primer búfer de la cadena de intercambio tiene lugar del búfer mostrado. Este proceso se denomina intercambio o volteo. Para obtener más información, consulte Cadenas de intercambio.
- El método Present de la interfaz IDXGISwapChain1 indica a DXGI que bloquee hasta que se produzca la sincronización vertical (VSync), poniendo la aplicación en suspensión hasta la siguiente VSync. Esto garantiza que no desperdicia ningún ciclo de representación de fotogramas que nunca se mostrarán en la pantalla.
- El método DiscardView de la interfaz ID3D11DeviceContext3 descarta el contenido del destino de representación. Se trata de una operación válida solo cuando el contenido existente se sobrescribirá por completo. Si se usan rectts de desplazamiento o sucios, se debe quitar esta llamada.
- Con el mismo método DiscardView , descarte el contenido de la galería de símbolos de profundidad.
- El método HandleDeviceLost se usa para administrar el escenario del dispositivo que se va a quitar. Si el dispositivo se quitó mediante una desconexión o una actualización del controlador, debe volver a crear todos los recursos del dispositivo. Para obtener más información, consulte Controlar escenarios eliminados de dispositivos en Direct3D 11.
Sugerencia
Para lograr una velocidad de fotogramas suave, debe asegurarse de que la cantidad de trabajo para representar un fotograma encaja en el tiempo entre VSyncs.
// Present the contents of the swap chain to the screen.
void DX::DeviceResources::Present()
{
// 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);
// Discard the contents of the render target.
// This is a valid operation only when the existing contents will be entirely
// overwritten. If dirty or scroll rects are used, this call should be removed.
m_d3dContext->DiscardView(m_d3dRenderTargetView.get());
// Discard the contents of the depth stencil.
m_d3dContext->DiscardView(m_d3dDepthStencilView.get());
// 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 || hr == DXGI_ERROR_DEVICE_RESET)
{
HandleDeviceLost();
}
else
{
winrt::check_hresult(hr);
}
}
Pasos siguientes
En este tema se explica cómo se representan los gráficos en la pantalla y se proporciona una breve descripción de algunos de los términos de representación usados (a continuación). Obtenga más información sobre la representación en el tema Rendering Framework II: Representación de juegos y aprenda a preparar los datos necesarios antes de la representación.
Términos y conceptos
Escena de juego simple
Una escena de juego simple se compone de algunos objetos con varias fuentes de luz.
La forma de un objeto se define mediante un conjunto de coordenadas X, Y, Z en el espacio. La ubicación de representación real en el mundo del juego se puede determinar aplicando una matriz de transformación a las coordenadas posicionales X, Y, Z. También puede tener un conjunto de coordenadas de textura (usted y V) que especifican cómo se aplica un material al objeto. Esto define las propiedades de superficie del objeto y le ofrece la capacidad de ver si un objeto tiene una superficie aproximada (como una bola de tenis) o una superficie brillante suave (como una bola de bolera).
El marco de representación usa la información de la escena y del objeto para volver a crear el marco de la escena por fotograma, lo que hace que esté activo en el monitor de visualización.
Canalización de representación
La canalización de representación es el proceso por el que la información de la escena 3D se traduce a una imagen mostrada en pantalla. En Direct3D 11, esta canalización es programable. Puede adaptar las fases para satisfacer sus necesidades de representación. Las fases que presentan núcleos comunes de sombreador se pueden programar mediante el lenguaje de programación HLSL. También se conoce como canalización de representación de gráficos o simplemente canalización.
Para ayudarle a crear esta canalización, debe estar familiarizado con estos detalles.
- HLSL. Te recomendamos el uso de HLSL Shader Model 5.1 y versiones posteriores para juegos DirectX para UWP.
- Sombreadores.
- Sombreadores de vértices y sombreadores de píxeles.
- Fases del sombreador.
- Varios formatos de archivo de sombreador.
Para obtener más información, consulte Descripción de la canalización de representación de Direct3D 11 y canalización de gráficos.
HLSL
HLSL es el lenguaje de sombreador de alto nivel para DirectX. Con HLSL, puede crear sombreadores programables similares a C para la canalización de Direct3D. Para obtener más información, consulte HLSL.
Sombreadores
Un sombreador se puede considerar como un conjunto de instrucciones que determinan cómo aparece la superficie de un objeto cuando se representa. Los que están programados mediante HLSL se conocen como sombreadores HLSL. Los archivos de código fuente para los sombreadores [HLSL])(#hlsl) tienen la .hlsl
extensión de archivo. Estos sombreadores se pueden compilar en tiempo de compilación o en tiempo de ejecución y establecerse en tiempo de ejecución en la fase de canalización adecuada. Un objeto de sombreador compilado tiene una .cso
extensión de archivo.
Los sombreadores direct3D 9 se pueden diseñar mediante el modelo de sombreador 1, el modelo de sombreador 2 y el modelo de sombreador 3; Los sombreadores direct3D 10 solo se pueden diseñar en el modelo de sombreador 4. Los sombreadores direct3D 11 se pueden diseñar en el modelo de sombreador 5. Direct3D 11.3 y Direct3D 12 se pueden diseñar en el modelo de sombreador 5.1 y Direct3D 12 también se puede diseñar en el modelo de sombreador 6.
Sombreadores de vértices y sombreadores de píxeles
Los datos entran en la canalización de gráficos como una secuencia de primitivos y los procesa varios sombreadores, como los sombreadores de vértices y los sombreadores de píxeles.
Los sombreadores de vértices procesan vértices, normalmente realizando operaciones como transformaciones, skinning e iluminación. Los sombreadores de píxeles permiten técnicas de sombreado enriquecidas, como la iluminación por píxel y el procesamiento posterior. Combina variables constantes, datos de textura, valores interpolados por vértice y otros datos para generar salidas por píxel.
Fases del sombreador
Una secuencia de estos distintos sombreadores definidos para procesar esta secuencia de primitivos se conoce como fases del sombreador en una canalización de representación. Las fases reales dependen de la versión de Direct3D, pero normalmente incluyen las fases de vértice, geometría y píxeles. También hay otras fases, como el casco y los sombreadores de dominio para la teselación y el sombreador de proceso. Todas estas fases son completamente programables mediante HLSL. Para obtener más información, consulte Canalización de gráficos.
Varios formatos de archivo de sombreador
Estas son las extensiones de archivo de código del sombreador.
- Un archivo con la
.hlsl
extensión contiene el código fuente [HLSL])(#hlsl). - Un archivo con la
.cso
extensión contiene un objeto sombreador compilado. - Un archivo con la
.h
extensión es un archivo de encabezado, pero en un contexto de código de sombreador, este archivo de encabezado define una matriz de bytes que contiene datos del sombreador. - Un archivo con la
.hlsli
extensión contiene el formato de los búferes de constantes. En el juego de ejemplo, el archivo es Shaders>ConstantBuffers.hlsli.
Nota:
Para insertar un sombreador, cargue un .cso
archivo en tiempo de ejecución o agregue un .h
archivo en el código ejecutable. Pero no usarías ambos para el mismo sombreador.
Comprensión más profunda de DirectX
Direct3D 11 es un conjunto de API que pueden ayudarnos a crear gráficos para aplicaciones de uso intensivo de gráficos, como juegos, donde queremos tener una buena tarjeta gráfica para procesar un cálculo intensivo. En esta sección se explican brevemente los conceptos de programación de gráficos de Direct3D 11: resource, subresource, device y device context.
Resource
Puede pensar en los recursos (también conocidos como recursos de dispositivo) como información sobre cómo representar un objeto, como textura, posición o color. Los recursos proporcionan datos a la canalización y definen lo que se representa durante la escena. Los recursos se pueden cargar desde el medio del juego o crearse dinámicamente en tiempo de ejecución.
Un recurso es, de hecho, un área en memoria a la que puede acceder la canalización de Direct3D. Para que la canalización pueda acceder a la memoria de forma eficiente, los datos que se proporcionan a la canalización (por ejemplo, geometría de entrada, recursos del sombreador y texturas) deben almacenarse en un recurso. Hay dos tipos de recursos de los que derivan todos los recursos de Direct3D: un búfer o una textura. Se pueden activar hasta 128 recursos para cada fase de la canalización. Para obtener más información, consulte Recursos.
Subrecurso
El término subrecurso hace referencia a un subconjunto de un recurso. Direct3D puede hacer referencia a un recurso completo o puede hacer referencia a subconjuntos de un recurso. Para obtener más información, consulte Subbresource.
Galería de símbolos de profundidad
Un recurso de galería de símbolos de profundidad contiene el formato y el búfer para contener información de profundidad y galería de símbolos. Se crea mediante un recurso de textura. Para obtener más información sobre cómo crear un recurso de galería de símbolos de profundidad, consulte Configuración de la funcionalidad de galería de símbolos de profundidad. Accedemos al recurso de galería de símbolos de profundidad a través de la vista de galería de símbolos de profundidad implementada mediante la interfaz ID3D11DepthStencilView .
La información de profundidad nos indica qué áreas de polígonos están detrás de otras, para que podamos determinar qué están ocultos. La información de galería de símbolos nos indica qué píxeles se enmascaran. Se puede usar para producir efectos especiales, ya que determina si se dibuja o no un píxel; establece el bit en 1 o 0.
Para obtener más información, vea Vista de galería de símbolos de profundidad, búfer de profundidad y búfer de galería de símbolos.
Destino de representación
Un destino de representación es un recurso en el que se puede escribir al final de un pase de representación. Normalmente se crea mediante el método ID3D11Device::CreateRenderTargetView mediante el búfer de retroceso de la cadena de intercambio (que también es un recurso) como parámetro de entrada.
Cada destino de representación también debe tener una vista de galería de símbolos de profundidad correspondiente porque cuando usamos OMSetRenderTargets para establecer el destino de representación antes de usarlo, también requiere una vista de galería de símbolos de profundidad. Accedemos al recurso de destino de representación a través de la vista de destino de representación implementada mediante la interfaz ID3D11RenderTargetView .
Dispositivo
Puede imaginar un dispositivo como una manera de asignar y destruir objetos, representar primitivos y comunicarse con la tarjeta gráfica a través del controlador de gráficos.
Para obtener una explicación más precisa, un dispositivo Direct3D es el componente de representación de Direct3D. Un dispositivo encapsula y almacena el estado de representación, realiza transformaciones y operaciones de iluminación y rasteriza una imagen en una superficie. Para obtener más información, consulte Dispositivos.
Un dispositivo se representa mediante la interfaz ID3D11Device. En otras palabras, la interfaz ID3D11Device representa un adaptador de pantalla virtual y se usa para crear recursos que son propiedad de un dispositivo.
Hay diferentes versiones de ID3D11Device. ID3D11Device5 es la versión más reciente y agrega nuevos métodos a los de ID3D11Device4. Para obtener más información sobre cómo Direct3D se comunica con el hardware subyacente, consulte Arquitectura de Windows Device Driver Model (WDDM).
Cada aplicación debe tener al menos un dispositivo; la mayoría de las aplicaciones crean solo una. Cree un dispositivo para uno de los controladores de hardware instalados en la máquina llamando a D3D11CreateDevice o D3D11CreateDeviceAndSwapChain y especificando el tipo de controlador con la marca D3D_DRIVER_TYPE. Cada dispositivo puede usar uno o varios contextos de dispositivo, en función de la funcionalidad deseada. Para obtener más información, vea D3D11CreateDevice function( Función D3D11CreateDevice).
Contexto del dispositivo
Se usa un contexto de dispositivo para establecer el estado de canalización y generar comandos de representación mediante los recursos propiedad de un dispositivo.
Direct3D 11 implementa dos tipos de contextos de dispositivo, uno para la representación inmediata y el otro para la representación diferida; ambos contextos se representan con una interfaz ID3D11DeviceContext .
Las interfaces ID3D11DeviceContext tienen versiones diferentes; ID3D11DeviceContext4 agrega nuevos métodos a los de ID3D11DeviceContext3.
ID3D11DeviceContext4 se introduce en el Actualización de Windows 10 para creadores y es la versión más reciente de la interfaz ID3D11DeviceContext. Las aplicaciones destinadas a Actualización de Windows 10 para creadores y versiones posteriores deben usar esta interfaz en lugar de versiones anteriores. Para obtener más información, vea ID3D11DeviceContext4.
DX::D eviceResources
La clase DX::D eviceResources está en los archivos DeviceResources.cpp.h/ y controla todos los recursos del dispositivo DirectX.
Búfer
Un recurso de búfer es una colección de datos completamente tipados agrupados en elementos. Puede usar búferes para almacenar una amplia variedad de datos, incluidos vectores de posición, vectores normales, coordenadas de textura en un búfer de vértices, índices en un búfer de índice o estado del dispositivo. Los elementos de búfer pueden incluir valores de datos empaquetados (como R8G8B8A8 valores de superficie), enteros de 8 bits únicos o cuatro valores de punto flotante de 32 bits.
Hay tres tipos de búferes disponibles: búfer de vértices, búfer de índice y búfer de constantes.
Búfer de vértices
Contiene los datos de vértices usados para definir la geometría. Los datos de vértice incluyen coordenadas de posición, datos de color, datos de coordenadas de textura, datos normales, etc.
Búfer de índice
Contiene desplazamientos enteros en búferes de vértices y se usan para representar primitivos de forma más eficaz. Un búfer de índice contiene un conjunto secuencial de índices de 16 o 32 bits; cada índice se usa para identificar un vértice en un búfer de vértices.
Búfer de constantes o búfer de constantes de sombreador
Permite proporcionar datos del sombreador de forma eficaz a la canalización. Puede usar búferes de constantes como entradas para los sombreadores que se ejecutan para cada primitivo y almacenar los resultados de la fase de salida de flujo de la canalización de representación. Conceptualmente, un búfer de constantes se parece a un búfer de vértices de un solo elemento.
Diseño e implementación de búferes
Puede diseñar búferes basados en el tipo de datos, por ejemplo, como en nuestro juego de ejemplo, se crea un búfer para los datos estáticos, otro para los datos que son constantes sobre el marco y otro para los datos específicos de un primitivo.
Todos los tipos de búfer se encapsulan mediante la interfaz ID3D11Buffer y puede crear un recurso de búfer llamando a ID3D11Device::CreateBuffer. Pero un búfer debe enlazarse a la canalización para poder acceder a él. Los búferes se pueden enlazar a varias fases de canalización simultáneamente para la lectura. Un búfer también se puede enlazar a una única fase de canalización para escribir; sin embargo, el mismo búfer no se puede enlazar para leer y escribir simultáneamente.
Puede enlazar búferes de estas maneras.
- Para la fase del ensamblador de entrada llamando a métodos ID3D11DeviceContext como ID3D11DeviceContext::IASetVertexBuffers e ID3D11DeviceContext::IASetIndexBuffer.
- Para la fase de salida de flujo llamando a ID3D11DeviceContext::SOSetTargets.
- Para la fase del sombreador llamando a métodos de sombreador, como ID3D11DeviceContext::VSSetConstantBuffers.
Para obtener más información, consulte Introducción a los búferes en Direct3D 11.
DXGI
Microsoft DirectX Graphics Infrastructure (DXGI) es un subsistema que encapsula algunas de las tareas de bajo nivel que necesita Direct3D. Se debe tener especial cuidado al usar DXGI en una aplicación multiproceso para asegurarse de que no se produzcan interbloqueos. Para obtener más información, consulta Multithreading y DXGI.
Nivel de característica
El nivel de característica es un concepto introducido en Direct3D 11 para controlar la diversidad de tarjetas de vídeo en máquinas nuevas y existentes. Un nivel de característica es un conjunto bien definido de funcionalidades de unidad de procesamiento de gráficos (GPU).
Cada tarjeta de vídeo implementa un determinado nivel de funcionalidad de DirectX en función de las GPU instaladas. En versiones anteriores de Microsoft Direct3D, podría averiguar la versión de Direct3D que implementó la tarjeta de vídeo y luego programar la aplicación en consecuencia.
Con el nivel de característica, al crear un dispositivo, puede intentar crear un dispositivo para el nivel de característica que desea solicitar. Si la creación del dispositivo funciona, ese nivel de característica existe, si no, el hardware no admite ese nivel de característica. Puede intentar volver a crear un dispositivo en un nivel de característica inferior o puede optar por salir de la aplicación. Por ejemplo, el nivel de característica 12_0 requiere Direct3D 11.3 o Direct3D 12 y el modelo de sombreador 5.1. Para obtener más información, consulte Niveles de características de Direct3D: Información general para cada nivel de característica.
Con los niveles de características, puede desarrollar una aplicación para Direct3D 9, Microsoft Direct3D 10 o Direct3D 11 y, a continuación, ejecutarla en hardware 9, 10 o 11 (con algunas excepciones). Para obtener más información, consulte Niveles de características de Direct3D.
Representación estéreo
La representación estéreo se usa para mejorar la ilusión de profundidad. Usa dos imágenes, una desde el ojo izquierdo y la otra desde el ojo derecho para mostrar una escena en la pantalla de visualización.
Matemáticamente, aplicamos una matriz de proyección estéreo, que es un ligero desplazamiento horizontal hacia la derecha y hacia la izquierda, de la matriz de proyección mono normal para lograr esto.
Hicimos dos pases de representación para lograr la representación estéreo en este juego de ejemplo.
- Enlace al destino de representación derecho, aplique la proyección correcta y, a continuación, dibuje el objeto primitivo.
- Enlace al destino de representación izquierdo, aplique proyección izquierda y, a continuación, dibuje el objeto primitivo.
Cámara y espacio de coordenadas
El juego tiene el código en su lugar para actualizar el mundo en su propio sistema de coordenadas (a veces denominado espacio mundial o espacio de escena). Todos los objetos, incluida la cámara, se colocan y orientan en este espacio. Para obtener más información, consulte Sistemas de coordenadas.
Un sombreador de vértices realiza el trabajo pesado de la conversión de las coordenadas del modelo a coordenadas del dispositivo con el siguiente algoritmo (donde V es un vector y M es una matriz).
V(device) = V(model) x M(model-to-world) x M(world-to-view) x M(view-to-device)
M(model-to-world)
es una matriz de transformación para las coordenadas del modelo a las coordenadas del mundo, también conocidas como matriz de transformación Mundo. Esto lo proporciona el primitivo.M(world-to-view)
es una matriz de transformación para que las coordenadas del mundo vean las coordenadas, también conocidas como matriz de transformación Ver.- Esto lo proporciona la matriz de vista de la cámara. Se define mediante la posición de la cámara junto con los vectores de mirada (el vector de mirada que apunta directamente a la escena desde la cámara y el vector de búsqueda que está hacia arriba perpendicular a ella).
- En el juego de ejemplo, m_viewMatrix es la matriz de transformación de vista y se calcula mediante Camera::SetViewParams.
M(view-to-device)
es una matriz de transformación para las coordenadas de vista a las coordenadas del dispositivo, también conocida como matriz de transformación proyección.- Esto lo proporciona la proyección de la cámara. Proporciona información sobre la cantidad de ese espacio realmente visible en la escena final. El campo de vista (FoV), la relación de aspecto y los planos de recorte definen la matriz de transformación de proyección.
- En el juego de ejemplo, m_projectionMatrix define la transformación a las coordenadas de proyección, calculadas mediante Camera::SetProjParams (Para proyección estéreo, se usan dos matrices de proyección, una para la vista de cada ojo).
El código del sombreador de VertexShader.hlsl
se carga con estos vectores y matrices de los búferes de constantes y realiza esta transformación para cada vértice.
Transformación de las coordenadas
Direct3D usa tres transformaciones para cambiar las coordenadas del modelo 3D en coordenadas de píxeles (espacio de pantalla). Estas transformaciones son transformación del mundo, transformación de vistas y transformación de proyección. Para obtener más información, consulta Introducción a la transformación.
Matriz de transformación del mundo
Una transformación del mundo cambia las coordenadas del espacio del modelo, donde los vértices se definen con respecto al origen local de un modelo, al espacio mundial, donde los vértices se definen en relación con un origen común a todos los objetos de una escena. En esencia, el mundo transforma un modelo en el mundo; por lo tanto, su nombre. Para obtener más información, vea Transformación del mundo.
Ver matriz de transformación
La transformación de vista localiza al visor en el espacio del mundo, transformando los vértices en el espacio de la cámara. En el espacio de la cámara, la cámara o el visor están en el origen, mirando en la dirección z positiva. Para obtener más información, vaya a Ver transformación.
Matriz de transformación de proyección
La transformación de proyección convierte el frustum de visualización en una forma cuboide. Un frustum de visualización es un volumen 3D en una escena situada en relación con la cámara de la ventanilla. Una ventanilla es un rectángulo 2D en el que se proyecta una escena 3D. Para obtener más información, consulte Ventanillas y recortes.
Dado que el extremo cercano del frustum de visualización es menor que el extremo lejano, esto tiene el efecto de expandir objetos que están cerca de la cámara; así es como se aplica la perspectiva a la escena. Por lo tanto, los objetos que están más cerca del reproductor aparecen más grandes; Los objetos que están más lejos aparecen más pequeños.
Matemáticamente, la transformación de proyección es una matriz que normalmente es una escala y una proyección de perspectiva. Funciona como la lente de una cámara. Para obtener más información, vea Transformación de proyección.
Estado del sampler
El estado del sampler determina cómo se muestrea los datos de textura mediante los modos de direccionamiento de textura, el filtrado y el nivel de detalle. El muestreo se realiza cada vez que se lee un píxel de textura (o textura) de una textura.
Una textura contiene una matriz de elementos de textura. La posición de cada elemento de textura se indica mediante (u,v)
, donde u
es el ancho y v
es el alto, y se asigna entre 0 y 1 en función del ancho y alto de la textura. Las coordenadas de textura resultantes se usan para abordar un elemento de textura al muestrear una textura.
Cuando las coordenadas de textura están por debajo de 0 o superior a 1, el modo de dirección de textura define cómo la coordenada de textura direcciona una ubicación de textura. Por ejemplo, al usar TextureAddressMode.Clamp, cualquier coordenada fuera del intervalo de 0 a 1 se fija en un valor máximo de 1 y el valor mínimo de 0 antes del muestreo.
Si la textura es demasiado grande o demasiado pequeña para el polígono, la textura se filtra para ajustarse al espacio. Un filtro de ampliación amplía una textura, un filtro de minificación reduce la textura para ajustarse a un área más pequeña. La ampliación de textura repite el elemento de textura de ejemplo para una o varias direcciones que produce una imagen más borrosa. La minificación de texturas es más complicada porque requiere combinar más de un valor de textura en un solo valor. Esto puede provocar el alias o bordes irregulares en función de los datos de textura. El enfoque más popular para la minificación es usar un mapa mip. Un mapa mip es una textura de varios niveles. El tamaño de cada nivel es una potencia de 2 menor que el nivel anterior hasta una textura de 1x1. Cuando se usa la minificación, un juego elige el nivel de mapa mip más cercano al tamaño que se necesita en tiempo de representación.
La clase BasicLoader
BasicLoader es una clase de cargador sencilla que proporciona compatibilidad para cargar sombreadores, texturas y mallas desde archivos en disco. Proporciona métodos sincrónicos y asincrónicos. En este juego de ejemplo, los BasicLoader.h/.cpp
archivos se encuentran en la carpeta Utilidades .
Para obtener más información, consulte Cargador básico.