Compartir a través de


Marco de representación I: Introducción a la representación

Nota:

Este tema forma parte de la serie de tutoriales Crear un sencillo juego 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 para la 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 cuadro, debes representar los objetos que son visibles en la escena, basándote en la perspectiva.

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.

  1. Establezca una conexión con la interfaz gráfica.
  2. Cree los recursos necesarios para dibujar los gráficos.
  3. 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 Renderizado II: La Renderización de Juegos aborda el paso 2: cómo configurar el marco de renderizado y cómo se preparan los datos antes de que pueda realizarse la renderización.

Comienza

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 términos de representación y gráficos 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 utiliza para recuperar la lista de objetos a renderizar, así como el estado del juego para el HUD (pantalla de visualización frontal).

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 el renderizado, consulta el tema Definir el marco de aplicaciones 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 para DX::DeviceResources, 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 la representación se inician en el método GameMain::Run, como se muestra a continuación.

El flujo simple es esto.

  1. Actualizar
  2. Renderizar
  3. Presente

Método GameMain::Run

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 de flujos de juegos para obtener más información sobre cómo se actualizan los estados del juego en el método GameMain::Update.

Representació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 el derecho. En cada paso de renderizado, enlazamos el destino de renderizado y la vista de profundidad-esténcil al dispositivo. También borramos la vista de galería de símbolos de profundidad después.

Nota:

La renderización estéreo se puede lograr mediante otros métodos, como el estéreo de paso único utilizando la instanciación de vértices o los sombreadores de geometría. El método de dos pasadas de renderizado es una manera más lenta pero más cómoda de lograr el renderizado estéreo.

Una vez que se ejecuta el juego y se cargan los recursos, actualizamos la matriz de proyección , una vez por paso de representación. Los objetos son ligeramente diferentes desde cada perspectiva. 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 los datos del búfer de vértices se canalizan en la tubería de renderizado de .

A continuación, establecemos el contexto de Direct3D para usar los búferes de constantes definidos anteriormente, que son utilizados por el sombreador de vértices en la fase de canalización y por el sombreador de píxeles en la fase de canalización .

Nota:

Consulta marco de representación 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 mundo del modelo e información de material.
  • El m_constantBufferChangesEveryPrim contiene parámetros para cada objeto. Incluye la matriz de transformación de objeto a mundo, así como propiedades del material, como el color y el exponente especular, para los cálculos de iluminación.
  • Establezca el contexto de Direct3D para usar el diseño de vértice de entrada de modo que los datos del objeto de malla se transmitan a la etapa de ensamblaje de entrada (IA) del pipeline de renderizado de .
  • Establezca el contexto de Direct3D para usar un de búfer de índice de en la fase de IA. Proporcione la información primitiva: tipo, orden de datos.
  • Hacer una llamada de dibujo para dibujar el primitivo indizado no instanciado. El método GameObject::Render actualiza el búfer de constantes primitivo con los datos específicos para un primitivo determinado. Esto da como resultado una llamada DrawIndexed en el contexto para dibujar la geometría de cada primitiva. En concreto, esta llamada encola comandos y datos a la tarjeta de procesamiento de gráficos (GPU), según lo 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 luego el sombreador de píxeles una vez por cada píxel de cada triángulo en la primitiva. 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 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 del búfer constante 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::Presentar

Llamamos al método DeviceResources::Present 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 fotograma para mostrar, el primer búfer de la cadena de intercambio toma el lugar del búfer mostrado. Este proceso se denomina intercambio o inversión. Para obtener más información, consulte cadenas de intercambio.

  • El método IDXGISwapChain1 interfaz Present indica a DXGI bloquear 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 de de la interfaz ID3D11DeviceContext3 Discard View descarta el contenido del destino de representación de . Se trata de una operación válida solo cuando el contenido existente se sobrescribirá por completo. Si se usan rectángulos sucios o de desplazamiento, se debe eliminar esta llamada.
  • Usando el mismo método DiscardView, descarte el contenido del depth-stencil .
  • El método HandleDeviceLost se usa para administrar el escenario del dispositivo que ha sido removido. 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 Gestionar escenarios de dispositivos retirados 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 marco de II: tema de representación de juegos, y aprenda cómo preparar los datos necesarios antes del proceso de 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—U y V—que especifican cómo se aplica un material al objeto. Esto define las propiedades superficiales del objeto y le brinda la capacidad de ver si un objeto tiene una superficie rugosa (como una pelota de tenis) o una superficie lisa y brillante (como una bola de boliche).

El marco de representación utiliza la información de la escena y del objeto para recrear el fotograma de la escena uno a uno, haciendo que cobre vida en su monitor.

Canalización de representación

El pipeline de renderizado es el proceso por el cual la información de la escena 3D se traduce en una imagen 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 la tubería de renderización gráfica , o simplemente tubería .

Para ayudarle a crear esta canalización, debe estar familiarizado con estos detalles.

Para obtener más información, consulte Descripción de la canalización de representación de Direct3D 11 y La canalización de gráficos.

HLSL

HLSL es el lenguaje de sombreador de alto nivel para DirectX. Usando HLSL, puede crear sombreadores programables similares al lenguaje C para el pipeline de Direct3D. Para obtener más información, vea 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 extensión de archivo .hlsl. Estos sombreadores se pueden compilar en tiempo de compilación o en tiempo de ejecución, y configurarse en tiempo de ejecución en la fase de canalización adecuada. Un objeto de sombreador compilado tiene una extensión de archivo .cso.

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 de los shaders.

  • Un archivo con la extensión .hlsl contiene el código fuente [HLSL])(#hlsl).
  • Un archivo con la extensión .cso contiene un objeto sombreador compilado.
  • Un archivo con la extensión .h 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 extensión .hlsli 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 archivo .cso en tiempo de ejecución o agregue un archivo .h 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.

Recurso

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 al pipeline y definen qué se muestra durante tu escena. Los recursos se pueden cargar desde el medio del juego o crearse dinámicamente en tiempo de ejecución.

De hecho, un recurso es un área en memoria a la que puede acceder la tubería de Direct3D . Para que la canalización acceda a la memoria de forma eficaz, los datos que se proporcionan a la canalización (como la geometría de entrada, los recursos del sombreador y las 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 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 el subrecurso .

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 Depth-Stencil. 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 plantilla 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 profundidad-esténcil, búfer de profundidady búfer de esténcil.

Destino de renderizado

Un destino de representación es un recurso al que podemos escribir al final de un pase de representación. Normalmente se crea mediante el método ID3D11Device::CreateRenderTargetView utilizando el búfer posterior 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 renderizado a través de la vista de renderizado 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 del modelo de controladores de dispositivos Windows (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 función D3D11CreateDevice.

Contexto del dispositivo

Se usa un contexto de dispositivo para establecer estado de canalización y generar comandos de representación mediante los recursos de 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 fue introducido en la Windows 10 Creators Update y es la versión más reciente de la interfaz de ID3D11DeviceContext. Las aplicaciones destinadas a Windows 10 Creators Update 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 de DeviceResources.cpp/.h y controla todos los recursos del dispositivo DirectX.

Memoria intermedia

Un recurso de búfer es un conjunto 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 suministrar datos de sombreado eficientemente 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 están encapsulados por la interfaz ID3D11Buffer y puede crear un recurso de búfer llamando a través de ID3D11Device::CreateBuffer. Pero un búfer debe enlazarse a la canalización para poder acceder a él. Los búferes se pueden vincular a varias etapas de la tubería simultáneamente y para su 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.

Usted puede enlazar búferes de las siguientes maneras.

  • Para la fase del ensamblador de entrada llamando a métodos ID3D11DeviceContext, como ID3D11DeviceContext::IASetVertexBuffers y ID3D11DeviceContext::IASetIndexBuffer.
  • En la etapa de salida de flujo, llamando a ID3D11DeviceContext::SOSetTargets.
  • Para acceder a la fase del sombreador llamando a métodos de sombreador, como ID3D11DeviceContext::VSSetConstantBuffers.

Para obtener más información, consulte Introducción a los buffers 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 funcionalidad, al crear un dispositivo, puede intentar crear un dispositivo para el nivel de funcionalidad 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 funcionalidad 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.

  • Vincula al objetivo de renderizado derecho, aplica la proyección derecha y luego dibuja el objeto primitivo.
  • Vincule al objetivo de renderizado izquierdo, aplique la proyección izquierda y luego 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 de coordenadas del modelo a coordenadas del mundo, también conocida como matriz de transformación Mundo . Esto lo proporciona el primitivo.
  • M(world-to-view) es una matriz de transformación para convertir coordenadas del mundo a coordenadas de vista, también conocida como la matriz de transformación de vista .
    • 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 visión (el vector mirar que apunta directamente a la escena desde la cámara, y el vector mirar hacia arriba que es perpendicular hacia arriba).
    • 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 de las coordenadas de vista a las coordenadas del dispositivo, también conocida como la matriz de transformación de 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 con Camera::SetProjParams (Para la 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 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 Descripción general de 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, la transformación mundial coloca un modelo en el mundo; de ahí su nombre. Para obtener más información, consulte World transform.

Ver matriz de transformación

La transformación de vista localiza al observador en el espacio mundial, transformando los vértices en el espacio de la cámara. En el espacio de la cámara, la cámara o el observador están en el origen, mirando hacia la dirección positiva del eje z. 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 posicionado en relación con la cámara del visor. Una ventanilla es un rectángulo 2D en el que se proyecta una escena 3D. Para obtener más información, consulte Viewports y clipping

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 combina 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 realiza el muestreo de los datos de textura mediante los modos de direccionamiento de textura, filtrado y nivel de detalle. El muestreo se realiza cada vez que se lee un píxel de textura (o texel) de una textura.

Una textura contiene una matriz de texeles. 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 utilizan para acceder a un texel en el muestreo de una textura.

Cuando las coordenadas de textura están por debajo de 0 o por encima de 1, el modo de dirección de textura define cómo la coordenada de textura direcciona una ubicación de texel. 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 texel de ejemplo para una o más direcciones, lo 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 texel en un solo valor. Esto puede provocar aliasing o bordes dentados según 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 necesario en tiempo de renderizado.

La clase BasicLoader

basicLoader es una clase de cargador sencilla que proporciona compatibilidad con la carga de sombreadores, texturas y mallas desde archivos en disco. Proporciona métodos sincrónicos y asincrónicos. En este juego de ejemplo, los archivos BasicLoader.h/.cpp se encuentran en la carpeta utilidades .

Para obtener más información, consulte el Loader Básico.