Adicionando conteúdo visual à amostra do Marble Maze

Applies to Windows only

Este documento descreve como o jogo Marble Maze usa Direct3D e Direct2D no ambiente de aplicativos da Windows Store para que você possa aprender os padrões e adaptá-los ao trabalhar com o seu próprio conteúdo de jogo. Para saber como os componentes de jogo visuais se encaixam na estrutura geral do aplicativo Marble Maze, veja Estrutura do aplicativo Marble Maze.

Seguimos estas etapas básicas à medida que desenvolvemos os aspectos visuais do Marble Maze:

  1. Crie uma estrutura básica que inicialize os ambientes Direct3D e Direct2D.
  2. Use programas de edição de imagens e modelos para projetar os ativos 2D e 3D que aparecem no jogo.
  3. Verifique se os ativos 2D e 3D são carregados corretamente e aparecem no jogo.
  4. Integre sombreadores de vértice e pixels capazes de melhorar a qualidade visual dos ativos do jogo.
  5. Integre a lógica do jogo, como animações e entradas do usuário.

Também nos concentramos primeiro em adicionar ativos 3D e, em seguida, ativos 2D. Por exemplo, nós nos concentramos na lógica central do jogo antes de adicionamos o sistema de menus e temporizador.

Também precisamos repetir algumas dessas etapas várias vezes durante o processo de desenvolvimento. Por exemplo, como fizemos mudanças nos modelos de malha e mármore, também tivemos que mudar uma parte do código do sombreador que dá suporte para esses modelos.

Observação  O código de amostra que corresponde a esse documento está disponível em Amostra do jogo DirectX Marble Maze.

Neste tópico

Veja a seguir alguns dos pontos-chave que este documento discute para quando você trabalhar com conteúdo DirectX e com conteúdo de jogos visuais, ou seja, quando você inicializar as bibliotecas de elementos gráficos DirectX, carregar recursos de cena e atualizar e renderizar a cena:

  • Em geral, adicionar conteúdo de jogo envolve várias etapas. Muitas vezes, essas etapas também exigem iteração. Frequentemente, desenvolvedores de jogos se concentram primeiro em adicionar conteúdo de jogo 3D e, em seguida, em adicionar conteúdo 2D.
  • Conquiste mais clientes e proporcione uma ótima experiência a todos eles dando suporte para a maior variedade possível de componentes de hardware gráfico.
  • Separe transparentemente os formatos de tempo de design e de tempo de execução. Estruture seus ativos de tempo de design para maximizar a flexibilidade e permitir rápidas iterações no conteúdo. Formate e compacte seus ativos para que eles sejam carregados e processados da maneira mais eficiente possível em tempo de execução.
  • Você cria os dispositivos Direct3D e Direct2D em um aplicativo da Windows Store de maneira bastante semelhante à que costuma fazer em um aplicativo de área de trabalho clássico do Windows. Uma diferença importante é a forma como a cadeia de permuta é associada à janela de saída.
  • Ao criar seu jogo, certifique-se de que o formato de malha escolhido dê suporte aos cenários principais. Por exemplo, se o seu jogo requer colisão, verifique se é possível obter dados de colisão a partir das suas malhas.
  • Separe a lógica do jogo da lógica de renderização atualizando primeiramente todos os objetos de cena antes de renderizá-los.
  • Em geral, você desenha seus objetos de cena 3D e, em seguida, desenha todos os objetos 2D que aparecem na frente da cena.
  • Sincronize o desenho com o espaço em branco vertical para garantir que o jogo não perca tempo desenhando quadros que não chegarão a aparecer na tela.

Introdução a elementos gráficos DirectX

Quando planejamos o jogo Marble Maze da Windows Store, escolhemos as linguagens C ++ e Direct3D 11.1 porque elas são as melhores opções para criar jogos 3D que exigem alto desempenho e o máximo de controle sobre o processo de renderização. O DirectX 11.1 dá suporte para componentes de hardware do DirectX 9, até o DirectX 11 e, portanto, pode ajudar você a conquistar mais clientes com maior eficiência, pois não é necessário reescrever o código para cada uma das versões anteriores do DirectX.

O Marble Maze usa o Direct3D 11.1 para renderizar os ativos de jogo 3D, ou seja, a bolinha e o labirinto. O Marble Maze também usa as linguagens Direct2D, DirectWrite e WIC (Windows Imaging Component) para desenhar os ativos de jogo 2D, como os menus e o temporizador. Por último, o Marble Maze usa o XAML para fornecer uma barra de aplicativos e permite a você adicionar controles XAML.

O processo de desenvolvimento de jogos requer planejamento. Se você não tem experiência com elementos gráficos DirectX, recomendamos a leitura do artigo "Criando um jogo DirectX para se familiarizar com os conceitos básicos da criação de um jogo DirectX da Windows Store". Ao ler esse documento e trabalhar com o código-fonte do Marble Maze, você pode consultar os seguintes recursos para obter informações mais detalhadas sobre elementos gráficos DirectX.

  • Elementos gráficos Direct3D 11 Descreve o Direct3D 11, uma poderosa API de elementos gráficos 3D com aceleração de hardware para a renderização da geometria 3D na plataforma Windows.
  • Direct2D Descreve o Direct2D uma API de elementos gráficos 2D com aceleração de hardware que fornece alto desempenho e renderização de alta qualidade para geometria 2D, bitmaps e texto.
  • DirectWrite Descreve a DirectWrite, que dá suporte para a renderização de texto de alta qualidade.
  • Windows Imaging Component Descreve o WIC, uma plataforma extensível que fornece uma API de baixo nível para imagens digitais.

Níveis de recursos

O Direct3D 11 apresenta um paradigma conhecido com níveis de recursos. Um nível de recurso é um conjunto bem definido de funcionalidades de GPU. Use níveis de recursos para direcionar seu jogo para execução em versões anteriores de componentes de hardware Direct3D. O Marble Maze dá suporte ao nível de recurso 9.1, pois não necessita dos recursos avançados dos níveis mais altos. Recomendamos que você dê suporte para a maior variedade possível de componentes de hardware e dimensione o conteúdo do seu jogo para que tanto os clientes com os computadores mais simples até aqueles com os computadores mais modernos possam ter uma grande experiência. Para saber mais sobre níveis de recursos, veja Direct3D 11 em componentes de hardware inferiores.

Inicializando o Direct3D e o Direct2D

Um dispositivo representa o adaptador de vídeo. Você cria os dispositivos Direct3D e Direct2D em um aplicativo da Windows Store de maneira bastante semelhante à que costuma fazer em um aplicativo de área de trabalho clássico do Windows. A principal diferença é em como você conecta a cadeia de permuta do Direct3D ao sistema de janelas.

O modelo de aplicativo DirectX (XAML)do Visual Studio calcula alguns sistemas de operação genéricos e funções de renderização 3D das funções específicas de jogo. A classe DeviceResources é uma base para o gerenciamento de Direct3D e Direct2D. Essa classe lida com a infraestrutura geral, e não com ativos específicos do jogo. O Marble Maze define a classe MarbleMaze para tratar ativos específicos do jogo, que fazem referência ao objeto DeviceResources para dar a ele acesso ao Direct3D e Direct2D.

Durante a inicialização, o método DeviceResources::Initialize cria recursos independentes do dispositivo e dos dispositivos Direct3D e Direct2D.


// Initialize the Direct3D resources required to run. 
void DeviceResources::DeviceResources(CoreWindow^ window, float dpi)
{
    m_window = window;

    CreateDeviceIndependentResources();
    CreateDeviceResources();
    CreateWindowSizeDependentResources();
    SetDpi(dpi);
}


A classe DeviceResources separa essa funcionalidade de modo que ela pode responder mais facilmente às mudanças no ambiente. Por exemplo, ela chama o método CreateWindowSizeDependentResources quando o tamanho da janela muda.

Inicializando os alocadores Direct2D, DirectWrite e WIC

O método DeviceResources::CreateDeviceIndependentResources cria os fatores para Direct2D, DirectWrite e WIC. Em elementos gráficos DirectX, alocadores são pontos de partida para a criação de recursos gráficos. O Marble Maze especifica D2D1_FACTORY_TYPE_SINGLE_THREADED porque ele realiza toda a atividade de desenho no thread principal.


// These are the resources required independent of hardware. 
void DeviceResources::CreateDeviceIndependentResources()
{
    D2D1_FACTORY_OPTIONS options;
    ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
     // If the project is in a debug build, enable Direct2D debugging via SDK Layers
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

    DX::ThrowIfFailed(
        D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            __uuidof(ID2D1Factory1),
            &options,
            &m_d2dFactory
            )
        );

    DX::ThrowIfFailed(
        DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory),
            &m_dwriteFactory
            )
        );

    DX::ThrowIfFailed(
        CoCreateInstance(
            CLSID_WICImagingFactory,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_wicFactory)
            )
        );
}

Criando os dispositivos Direct3D e Direct2D

O método DeviceResources::CreateDeviceResources aciona D3D11CreateDevice para criar o objeto do dispositivo que representa um adaptador de exibição Direct3D. Como o Marble Maze tem suporte para o recursos de nível 9.1 e superior, o método DeviceResources::CreateDeviceResources especifica os níveis 9.1 até 11.1 na gama de valores \. O Direct3D percorre a lista em ordem e fornece ao aplicativo o primeiro nível de recurso disponível. Portanto, as entradas da matriz de D3D_FEATURE_LEVEL são listadas da maior para a menor, de tal forma que o aplicativo terá o nível de recurso mais alto disponível. O método DeviceResources::CreateDeviceResources obtém o dispositivo Direct3D 11.1 ao consultar o dispositivo Direct3D 11 que é retornado de D3D11CreateDevice.


// This array defines the set of DirectX hardware feature levels this app will support. 
// Note the ordering should be preserved. 
// Don't forget to declare your application's minimum required feature level in its 
// description.  All applications are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] = 
{
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_1
};

// Create the DX11 API device object, and get a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
DX::ThrowIfFailed(
    D3D11CreateDevice(
        nullptr,                    // specify null to use the default adapter
        D3D_DRIVER_TYPE_HARDWARE,
        0,                          // leave as 0 unless software device
        creationFlags,              // optionally set debug and Direct2D compatibility flags
        featureLevels,              // list of feature levels this app can support
        ARRAYSIZE(featureLevels),   // number of entries in above list
        D3D11_SDK_VERSION,          // always set this to D3D11_SDK_VERSION for modern
        &device,                    // returns the Direct3D device created
        &m_featureLevel,            // returns feature level of device created
        &context                    // returns the device immediate context
        )
    );    

// Get the Direct3D 11.1 device by querying the Direct3D 11 device.
DX::ThrowIfFailed(
    device.As(&m_d3dDevice)
    );

O método DeviceResources::CreateDeviceResources cria então o dispositivo Direct2D. O Direct2D usa o DXGI (Microsoft DirectX Graphics Infrastructure) para interoperar com o Direct3D. O DXGI permite que superfícies de memória de vídeo sejam compartilhadas entre tempos de execução de elementos gráficos. O Marble Maze usa o dispositivo DXGI subjacente do dispositivo Direct3D para criar o dispositivo Direct2D a partir do alocador Direct2D.


// Obtain the underlying DXGI device of the Direct3D 11.1 device.
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

// Obtain the Direct2D device for 2-D rendering.
DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

// And get its corresponding device context object.
DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );


// Obtain the underlying DXGI device of the Direct3D 11.1 device.
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

// Obtain the Direct2D device for 2-D rendering.
DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

// And get its corresponding device context object.
DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );


Para saber mais sobre o DXGI e a interoperabilidade entre o Direct2D e o Direct3D, consulte Visão geral da DXGI e Visão geral do Direct2D e do Direct3D.

Associando o Direct3D à exibição

O método DeviceResources::CreateWindowSizeDependentResources cria os recursos gráficos que dependem de um tamanho de janela determinado, como a cadeia de permuta e os alvos de renderização do Direct3D e Direct2D. Uma característica importante que diferencia um aplicativo DirectX da Windows Store de um aplicativo de área de trabalho é a forma como a cadeia de permuta é associada à janela de saída. Uma cadeia de permuta é responsável por exibir o buffer para o qual o dispositivo é renderizado no monitor. O documento sobre a estrutura do aplicativo Marble Maze descreve como o sistema de janelas para um aplicativo da Windows Store difere de um aplicativo de área de trabalho. Como um aplicativo da Windows Store não funciona com objetos HWND, o Marble Maze deve usar o método IDXGIFactory2::CreateSwapChainForCoreWindow para associar a saída do dispositivo à exibição. O exemplo a seguir mostra a parte do método DeviceResources::CreateWindowSizeDependentResources que cria a cadeia de permuta.


// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
    dxgiFactory->CreateSwapChainForCoreWindow(
        m_d3dDevice.Get(),
        reinterpret_cast<IUnknown*>(m_window),
        &swapChainDesc,
        nullptr,    // allow on all displays
        &m_swapChain
        )
    );

Para minimizar o consumo de energia, o que é importante para dispositivos movidos a bateria como laptops e tablets, o método DeviceResources::CreateWindowSizeDependentResources aciona o método IDXGIDevice1::SetMaximumFrameLatency para assegurar que o jogo é renderizado somente depois do branco vertical. A sincronização com o espaço em branco vertical é descrita com mais detalhes na secção "Apresentando a cena", neste documento.


// Ensure that DXGI does not queue more than one frame at a time. This both reduces  
// latency and ensures that the application will only render after each VSync, minimizing  
// power consumption.
DX::ThrowIfFailed(
    dxgiDevice->SetMaximumFrameLatency(1)
    );


O método DeviceResources::CreateWindowSizeDependentResources inicializa os recursos gráficos de um modo que funciona para a maioria dos jogos. Para conhecer outro exemplo que mostra como inicializar um aplicativo DirectX da Windows Store, veja Como configurar seu aplicativo DirectX da Windows Store para mostrar uma exibição.

Observação  O termo exibição tem um significado diferente no Tempo de Execução do Windows do que no Direct3D. No Tempo de Execução do Windows, uma exibição refere-se ao conjunto de configurações de interface do usuário para um aplicativo, incluindo a área de exibição e os comportamentos de entrada, além do thread utilizado para processamento. Você pode especificar as configurações necessárias ao criar uma exibição. O processo de configurar uma exibição de aplicativo é descrito em Estrutura do aplicativo Marble Maze. No Direct3D, o termo exibição tem vários significados. Em primeiro lugar, uma exibição de recurso define os sub-recursos que um recurso pode acessar. Por exemplo, quando um objeto de textura está associado a uma exibição de recurso de sombreador, esse sombreador pode acessar a textura mais tarde. Uma das vantagens de uma exibição de recurso é que você pode interpretar os dados de diferentes maneiras em diferentes estágios no pipeline de renderização. Para saber mais sobre exibições de recursos, veja Exibições de textura (Direct3D 10). Quando usada no contexto de uma transformação de exibição ou de uma matriz de transformação de exibição, uma exibição refere-se à localização e à orientação da câmara. Uma transformação de exibição realoca objetos ao redor da posição e da orientação da câmera. Para saber mais sobre transformações de exibição, veja Transformação de exibição (Direct3D 9). A forma como o Marble Maze usa exibições de recurso e matriz é descrita com mais detalhes neste tópico.

Carregando recursos de cena

O Marble Maze usa a classe BasicLoader, que é declarada em BasicLoader.h, para carregar texturas e sombreadores. O Marble Maze usa a classe SDKMesh para carregar as malhas 3D para o labirinto e a bolinha.

Para garantir um aplicativo ágil, o Marble Maze carrega recursos de cena de forma assíncrona, ou em segundo plano. Como os ativos são carregados em segundo plano, seu jogo pode responder a eventos de janela. Esse processo é explicado com mais detalhes em Carregando ativos de jogo em segundo plano, neste guia.

Carregando a sobreposição 2D e a interface do usuário

No Marble Maze, a sobreposição é a imagem que aparece na parte superior da tela. A sobreposição sempre aparece na frente da cena. No Marble Maze, a sobreposição contém o logotipo do Windows e a cadeia de texto "Amostra do jogo DirectX Marble Maze". O gerenciamento da sobreposição é executado pela classe SampleOverlay, que é definida em SampleOverlay.h. Esse código se assemelha ao código que aparece nas amostras Direct3D da Windows Store. Apesar de usarmos a sobreposição como parte das amostras Direct3D, você pode adaptar esse código para exibir qualquer imagem que aparece na frente da sua cena.

Um aspecto importante da sobreposição é que, como seu conteúdo não muda, a classe SampleOverlay desenha, ou armazena em cache, seu conteúdo para um objecto ID2D1Bitmap1 durante a inicialização. Na ocasião do sorteio, a classe SampleOverlay precisa desenhar o bitmap para a tela. Dessa forma, rotinas caras, como o desenho de texto, não precisam ser executadas para cada quadro.

A interface de usuário consiste em componentes 2D, como menus e HUDs (exibições de alerta), que aparecem na frente da sua cena. O Marble Maze define os seguintes elementos de interface do usuário:

  • Itens de menu que permitem ao usuário iniciar o jogo ou visualizar as pontuações mais altas.
  • Um temporizador com uma contagem regressiva de três segundos antes de o jogo começar.
  • Um temporizador que controla o tempo de jogo decorrido.
  • Uma tabela que lista os tempos de finalização mais rápidos.
  • O texto que indica "Pausado" quando o jogo está pausado.

O Marble Maze define elementos de interface do usuário específicos de jogo em UserInterface.h. O Marble Maze define a classe ElementBase como o tipo base para todos os elementos da interface do usuário. A classe ElementBase define atributos, como o tamanho, a posição, o alinhamento e a visibilidade de um elemento de interface do usuário. Ele também controla a forma como os elementos são atualizados e renderizados.


class ElementBase
{
public:
    virtual void Initialize() { }
    virtual void Update(float timeTotal, float timeDelta) { }
    virtual void Render() { }

    void SetAlignment(AlignType horizontal, AlignType vertical);
    virtual void SetContainer(const D2D1_RECT_F& container);
    void SetVisible(bool visible);

    D2D1_RECT_F GetBounds();

    bool IsVisible() const { return m_visible; }

protected:
    ElementBase();

    virtual void CalculateSize() { }

    Alignment       m_alignment;
    D2D1_RECT_F     m_container;
    D2D1_SIZE_F     m_size;
    bool            m_visible;
};

Ao fornecer uma classe base comum para elementos de interface do usuário, a classe UserInterface, que gera a interface do usuário, só precisa manter uma coleção de objetos ElementBase, o que simplifica o gerenciamento da interface do usuário e fornece um gerenciador de interface do usuário que pode ser reutilizado. O Marble Maze define tipos que são derivados de ElementBase e implementam comportamentos específicos de jogo. Por exemplo, HighScoreTable define o comportamento para a tabela de pontuações altas. Para saber mais sobre esses tipos, veja o código-fonte.

Observação  Como o XAML facilita a criação de interfaces de usuário complexas, como as encontradas em jogos de simulação e estratégia, considere a possibilidade de usá-lo para definir a sua interface do usuário. Para obter informações sobre como desenvolver uma interface de usuário em XAML em um jogo DirectX da Windows Store, veja Estender a amostra do jogo (Windows). Esse documento se refere à amostra de jogo de tiro DirectX 3-D.

Carregando sombreadores

O Marble Maze usa o método BasicLoader::LoadShader para carregar um sombreador a partir de um arquivo.

Sombreadores são a unidade fundamental da programação de GPU nos jogos de hoje. Quase todo o processamento de elementos gráficos 3D é realizado através de sombreadores, independentemente de ser uma transformação de modelo e uma iluminação de cena ou um processamento de geometria mais complexo, desde a descamação de personagens até a infusão de mosaicos. Para saber mais sobre o modelo de programação de sombreadores, consulte HLSL.

O Marble Maze usa sombreadores de vértice e de pixel. Um sombreador de vértice sempre opera em um vértice de entrada e produz um vértice como saída. Um sombreador de pixel usa valores numéricos, dados de textura, valores interpolados por vértice e outros dados para produzir uma cor de pixel como saída. Como um sombreador transforma um elemento de cada vez, o hardware de elementos gráficos que fornece vários pipelines de sombreador pode processar conjuntos de elementos em paralelo. O número de pipelines paralelos que estão disponíveis para a GPU pode ser muito maior que o número disponível para a CPU. Portanto, até mesmo sombreadores básicos podem melhorar significativamente a taxa de transferência.

O método MarbleMaze::LoadDeferredResources carrega um sombreador de vértice e um sombreador de pixel depois de carregar a sobreposição. As versões em tempo de design desses sombreadores estão definidas em BasicVertexShader.hlsl e BasicPixelShader.hlsl, respectivamente. O Marble Maze aplica esses sombreadores à bola e ao labirinto durante a fase de renderização.

O projeto do Marble Maze inclui tanto a versão .hlsl (o formato em tempo de design) quanto a versão .cso (o formato em tempo de execução) dos arquivos de sombreador. Em tempo de compilação, o Visual Studio usa o compilador de efeitos fxc.exe para compilar seu arquivo de origem .hlsl em um sombreador .cso. Para saber mais sobre a ferramenta de compilador de efeitos, veja Ferramenta de Compilador de Efeitos.

O sombreador de vértice usa as matrizes fornecidas de modelo, exibição e projeção para transformar a geometria de entrada. Os dados de posição da geometria de entrada são transformados e processados duas vezes: uma vez no espaço da tela, o que é necessário para o processamento, e novamente no espaço universal para permitir que o sombreador de pixel realize cálculos de iluminação. O vetor da normal da superfície é transformado em espaço universal, que também é usado pelo sombreador de pixel para iluminação. As coordenadas de textura são transmitidas em formato inalterado ao sombreador de pixel.


sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}


O sombreador de pixel recebe a saída do sombreador de vértice como entrada. Este sombreador realiza cálculos de iluminação para simular uma luz de projetor com contornos suaves que paira sobre o labirinto e fica alinhada à posição do mármore. A iluminação é mais forte para superfícies que apontam diretamente para a luz. O componente difuso é afunilado até zero à medida que a normal da superfície se torna perpendicular à luz, e o termo ambiente diminui à medida que a normal aponta para longe da luz. Os pontos mais próximos da bolinha (e, portanto, mais próximos do centro da luz do projetor) são iluminados com mais intensidade. No entanto, a iluminação é modulada para pontos abaixo da bolinha para simular uma sombra suave. Em um ambiente real, um objeto como a bolinha branca refletiria difusamente a luz de projetor em outros objetos na cena. Isso é aproximado para as superfícies que estão em exibição na metade iluminada da bolinha. Os fatores de iluminação adicionais estão a um ângulo e a uma distância relativos à bolinha. A cor do pixel resultante é uma composição da textura da amostra com o resultado dos cálculos de iluminação.


float4 main(sPSInput input) : SV_TARGET
{
    float3 lightDirection = float3(0, 0, -1);
    float3 ambientColor = float3(0.43, 0.31, 0.24);
    float3 lightColor = 1 - ambientColor;
    float spotRadius = 50;

    // Basic ambient (Ka) and diffuse (Kd) lighting from above.
    float3 N = normalize(input.norm);
    float NdotL = dot(N, lightDirection);
    float Ka = saturate(NdotL + 1);
    float Kd = saturate(NdotL);

    // Spotlight.
    float3 vec = input.worldPos - marblePosition;
    float dist2D = sqrt(dot(vec.xy, vec.xy));
    Kd = Kd * saturate(spotRadius / dist2D);

    // Shadowing from ball.
    if (input.worldPos.z > marblePosition.z)
        Kd = Kd * saturate(dist2D / (marbleRadius * 1.5));

    // Diffuse reflection of light off ball.
    float dist3D = sqrt(dot(vec, vec));
    float3 V = normalize(vec);
    Kd += saturate(dot(-V, N)) * saturate(dot(V, lightDirection))
        * saturate(marbleRadius / dist3D);

    // Final composite.
    float4 diffuseTexture = Texture.Sample(Sampler, input.tex);
    float3 color = diffuseTexture.rgb * ((ambientColor * Ka) + (lightColor * Kd));
    return float4(color * lightStrength, diffuseTexture.a);
}


Cuidado  O sombreador de pixel compilado contém 32 instruções aritméticas e uma instrução de textura. Esse sombreador terá um bom desempenho em desktops e em tablets modernos. No entanto, um computador mais simples pode não ser capaz de processar esse sombreador e ainda fornecer uma taxa de quadros interativa. Considere o hardware típico do seu público-alvo e projetar seus sombreadores de forma a corresponder aos recursos desse hardware.

O método MarbleMaze::LoadDeferredResources usa o método BasicLoader::LoadShader para carregar os sombreadores. O exemplo a seguir carrega o sombreador de vértice. O formato de tempo de execução para esse sombreador é BasicVertexShader.cso. A variável do membro m_vertexShader é um objeto ID3D11VertexShader.


\loader->LoadShader(
    L"BasicVertexShader.cso",
    layoutDesc,
    ARRAYSIZE(layoutDesc),
    &m_vertexShader,
    &m_inputLayout
    );


A variável do membro m_inputLayout é um objeto ID3D11InputLayout. O objeto de layout de entrada encapsula o estado de entrada do estágio IA (assembler de entrada). Um dos trabalhos do estágio IA é tornar os sombreadores mais eficientes, usando valores gerados pelo sistema, também conhecidos como semânticas, para processar apenas as primitivas ou os vértices que ainda não foram processados. Use o método ID3D11Device::CreateInputLayout para criar um layout de entrada a partir de uma matriz de descrições de elementos de entrada. A matriz contém um ou mais elementos de entrada, e cada um destes descreve um elemento de dados de vértice a partir de um buffer de vértices. O conjunto inteiro de descrições de elementos de entrada descreve todos os elementos de dados de vértice de todos os buffers de vértices que serão associados ao estágio IA. O exemplo a seguir mostra a descrição de layout usada pelo Marble Maze. Essa descrição de layout descreve um buffer de vértices que contém quatro elementos de dados de vértice. As partes importantes de cada entrada na matriz são o nome semântica, o formato de dados e o deslocamento de bytes. Por exemplo, o elemento POSITION especifica a posição do vértice no espaço do objeto. Ele começa no deslocamento de byte 0 e contém três componentes de ponto flutuante (para um total de 12 bytes). O elemento NORMAL especifica o vetor da normal. Ele começa no deslocamento de byte 12 porque aparece logo depois de POSITION no layout, o que requer 12 bytes. O elemento NORMAL contém um quatro componente, um inteiro sem sinal de 32 bits.


D3D11_INPUT_ELEMENT_DESC layoutDesc[] = 
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL",   0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD",  0, DXGI_FORMAT_R32G32_FLOAT,   0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT,  0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 }, 
};
m_vertexStride = 44; // must set this to match the size of layoutDesc above

Compare o layout de entrada com a estrutura de sVSInput definida pelo sombreador de vértice, conforme mostrado no exemplo a seguir. A estrutura sVSInput define os elementos POSITION, NORMAL e TEXCOORD0. O tempo de execução DirectX mapeia cada elemento no layout para a estrutura de entrada definida pelo sombreador.


struct sVSInput
{
    float3 pos : POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
};

struct sPSInput
{
    float4 pos : SV_POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
    float3 worldPos : TEXCOORD1;
};

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}


O documento Semânticas descreve com mais detalhes cada uma das semânticas disponíveis.

Observação  Em um layout, você pode especificar componentes adicionais que não são usados para permitir que vários sombreadores compartilhem o mesmo layout. Por exemplo, o elemento TANGENT não é usado pelo sombreador. Você poderá usar o elemento TANGENT se você quiser testar técnicas como o mapeamento de normais. Usando o mapeamento de normais, também conhecido como mapeamento de impacto, você pode criar o efeito de impactos nas superfícies de objetos. Para saber mais sobre o mapeamento de impacto, veja Mapeamento de impacto (Direct3D 9).

Para saber mais sobre o estado do estágio do assembly de entrada, veja Estágio do Assembler de Entrada e Introdução ao estágio do Assembler de Entrada.

O processo de usar os sombreadores de vértice e de pixel para renderizar a cena está descrito na secção Renderizando a cena, mais adiante neste documento.

Criando o buffer constante

O buffer Direct3D agrupa uma coleção de dados. Um buffer constante é uma espécie de buffer que pode ser usado para transmitir dados a sombreadores. O Marble Maze usa um buffer constante para manter a exibição do modelo (ou universal) e as matrizes de projeção para o objeto de cena ativo.

O exemplo a seguir mostra como o método MarbleMaze::LoadDeferredResources cria um buffer constante que, mais tarde, irá conter dados de matriz. O exemplo cria uma estrutura D3D11_BUFFER_DESC que usa o sinalizador D3D11_BIND_CONSTANT_BUFFER para especificar o uso como um buffer constante. Em seguida, o exemplo transmite essa estrutura ao método ID3D11Device::CreateBuffer. A variável m_constantBuffer é um objeto ID3D11Buffer.


// create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};
constantBufferDesc.ByteWidth           = ((sizeof(ConstantBuffer) + 15) / 16) * 16; // multiple of 16 bytes
constantBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags           = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags      = 0;
constantBufferDesc.MiscFlags           = 0;
// this will not be used as a structured buffer, so this parameter is ignored
constantBufferDesc.StructureByteStride = 0;

DX::ThrowIfFailed(
    m_d3dDevice->CreateBuffer(
        &constantBufferDesc,
        nullptr,             // leave the buffer uninitialized
        &m_constantBuffer
        )
    );

Mais tarde, o método MarbleMaze::Update atualiza objetos ConstantBuffer, um para o labirinto e outro para a bolinha. O método MarbleMaze::Render então associa cada objeto ConstantBuffer ao buffer constante antes que esses objetos sejam processados. O exemplo a seguir mostra a estrutura ConstantBuffer em MarbleMaze.h.


// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
    float4x4 model;
    float4x4 view;
    float4x4 projection;

    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};


Para entender melhor como buffers constantes mapeiam o código do sombreador, compare a estrutura de ConstantBuffer com o buffer constante SimpleConstantBuffer que é definido pelo sombreador de vértice em BasicVertexShader.hlsl:


cbuffer ConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};


O layout da estrutura ConstantBuffer corresponde ao objeto cbuffer. A variável cbuffer especifica o registo b0, o que significa que os dados do buffer constante são armazenados no registro 0. O método MarbleMaze::Render especifica o registro 0 quando ativa o buffer constante. Esse processo é descrito com mais detalhes em uma seção posterior deste documento.

Para saber mais sobre buffers constantes, veja Introdução a buffers no Direct3D 11. Para saber mais sobre a palavra-chave do registro, veja register.

Carregando malhas

O Marble Maze usa o SDK-Mesh como formato de tempo de execução porque esse formato fornece uma maneira básica de carregar dados de malha para aplicativos de amostra. Para uso em produção, você deve usar um formato de malha que atenda aos requisitos específicos do seu jogo.

O método MarbleMaze::LoadDeferredResources carrega dados de malha depois de carregar os sombreadores de vértice e pixel. Uma malha é uma coleção de dados de vértice que muitas vezes inclui informações como posições, dados de normais, cores, materiais e coordenadas de textura. Malhas são tipicamente criadas em softwares de autoria de 3D e mantidos em arquivos que são separados do código do aplicativo. O mármore e o labirinto são dois exemplos de malhas que o jogo usa.

Observação  O Marble Maze usa o tipo de arquivo .fbx (Autodesk FBX Interchange File) como formato de tempo de design e o tipo de arquivo .sdkmesh (SDK-Mesh) como o formato de tempo de execução. O formato SDK-Mesh também é usado em outras amostras DirectX. SDK-Mesh é um formato binário que fornece uma forma básica de carregar dados de malha. Para saber mais sobre o formato SDK Mesh, veja Atualização do Exportador de Conteúdo de Amostras.

O Marble Maze usa a classe SDKMesh para gerenciar malhas. Essa classe é declarada em SDKMesh.h. SDKMesh fornece métodos para carregar, renderizar e destruir dados de malha.

Importante  O Marble Maze usa o formato SDK-Mesh e fornece a classe SDKMesh apenas para ilustração. Embora o formato SDK-Mesh seja útil para aprendizagem e para a criação de protótipos, ele é um formato muito básico que pode não atender aos requisitos da maior parte do desenvolvimento de um jogo. Recomendamos o uso de um formato de malha que atenda aos requisitos específicos do seu jogo.

O exemplo a seguir mostra como o método MarbleMaze::LoadDeferredResources usa o método SDKMesh::Create para carregar dados de malha para o labirinto e a esfera.


// Load the meshes.
DX::ThrowIfFailed(
    m_mazeMesh.Create(
        m_d3dDevice.Get(),
        L"Media\\Models\\maze1.sdkmesh",
        false
        )
    );

DX::ThrowIfFailed(
    m_marbleMesh.Create(
        m_d3dDevice.Get(),
        L"Media\\Models\\marble2.sdkmesh",
        false
        )
    );

Carregando dados de colisão

Embora esta seção não se concentre em como o Marble Maze implementa a simulação de física entre a bolinha e o labirinto, observe que a geometria de malha para o sistema de física é lida quando as malhas são carregadas.


// Extract mesh geometry for physics system.
DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_walls",
        m_collision.m_wallTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_Floor",
        m_collision.m_groundTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_floorSides",
        m_collision.m_floorTriList
        )
    );

m_physics.SetCollision(&m_collision);
float radius = m_marbleMesh.GetMeshBoundingBoxExtents(0).x / 2;
m_physics.SetRadius(radius);


A maneira como você carrega dados de colisões depende amplamente do formato de tempo de execução utilizado. Para saber mais sobre como o Marble Maze carrega a geometria de colisão a partir de um arquivo SDK-Mesh, veja o método MarbleMaze::ExtractTrianglesFromMesh no código-fonte.

Atualizando o estado do jogo

O Marble Maze separa a lógica do jogo da lógica de renderização atualizando primeiramente todos os objetos de cena antes de renderizá-los.

A documento sobre a estrutura do aplicativo Marble Maze descreve o loop principal do jogo. A atualização da cena, o que faz parte do loop do jogo, acontece depois que a entrada e os eventos do Windows são processados e antes que a cena seja renderizada. O método MarbleMaze::Update lida com a atualização da interface do usuário e do jogo.

Atualizando a interface de usuário

O método MarbleMaze::Update chama o método UserInterface::Update para atualizar o estado da interface do usuário.


UserInterface::GetInstance().Update(timeTotal, timeDelta);


O método UserInterface::Update atualiza cada elemento no conjunto da interface do usuário.


void UserInterface::Update(float timeTotal, float timeDelta)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        (*iter)->Update(timeTotal, timeDelta);
    }
}


Classes que são derivadas de ElementBase implementam o método Update para realizar comportamentos específicos. Por exemplo, o método StopwatchTimer::Update atualiza o tempo decorrido com base no valor fornecido e atualiza o texto que ele exibe mais tarde.


void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
    if (m_active)
    {
        m_elapsedTime += timeDelta;

        WCHAR buffer[16];
        GetFormattedTime(buffer);
        SetText(buffer);
    }

    TextElement::Update(timeTotal, timeDelta);
}


Atualizando a cena

O método MarbleMaze::Update atualiza o jogo com base no estado atual da máquina. Quando o jogo está no estado ativo, o Marble Maze atualiza a câmera para acompanhar a bolinha, atualiza a parte da matriz de exibição dos buffers constantes e atualiza a simulação da física.

O exemplo a seguir mostra como o método MarbleMaze::Update atualiza a posição da câmara. O Marble Maze usa a variável m_resetCamera para sinalizar que a câmera deve ser redefinida de forma a ser colocada diretamente sobre a bolinha. A câmera é redefinida quando o jogo começa ou quando a bolinha cai no labirinto. Quando o menu principal ou a tela de exibição de pontuações altas está ativo, a câmera fica definida em uma localização constante. Caso contrário, o Marble Maze usará o parâmetro timeDelta para interpolar a posição da câmara entre sua posição atual e as posições de destino. A posição de destino está ligeiramente acima e na frente da bolinha. Usar o período de tempo decorrido permite que a câmera acompanhe de forma gradual, ou persiga, a bolinha.


static float eyeDistance = 200.0f;
static float3 eyePosition = float3(0, 0, 0);

// Gradually move the camera above the marble.
float3 targetEyePosition = marblePosition - (eyeDistance * float3(g.x, g.y, g.z));
if (m_resetCamera)
{
    eyePosition = targetEyePosition;
    m_resetCamera = false;
}
else
{
    eyePosition = eyePosition + ((targetEyePosition - eyePosition) * min(1, timeDelta * 8));
}

// Look at the marble. 
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
    // Override camera position for menus.
    eyePosition = marblePosition + float3(75.0f, -150.0f, -75.0f);
    m_camera->SetViewParameters(eyePosition, marblePosition, float3(0.0f, 0.0f, -1.0f));
}
else
{
    m_camera->SetViewParameters(eyePosition, marblePosition, float3(0.0f, 1.0f, 0.0f));
}


O exemplo a seguir mostra como o método MarbleMaze::Update atualiza os buffers constantes para a bolinha e o labirinto. A matriz de modelo, ou universal, do labirinto é sempre a matriz de identidade. Exceto para a diagonal principal, cujos elementos são todos 1, a matriz de identidade é uma matriz quadrada formada por zeros. A matriz de modelo da bolinha se baseia em sua matriz de posição vezes a sua matriz de rotação. As funções mul e translation são definidas em BasicMath.h.


// Update the model matrices based on the simulation.
m_mazeConstantBufferData.model = identity();
m_marbleConstantBufferData.model = mul(
    translation(marblePosition.x, marblePosition.y, marblePosition.z),
    marbleRotationMatrix
    );

// Update the view matrix based on the camera.
float4x4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;


Para saber mais sobre como o método MarbleMaze::Update lê a entrada do usuário e simula o movimento da bolinha, veja Adicionando entrada e interatividade à amostra do Marble Maze.

Renderizando a cena

Quando a cena é renderizada, estas etapas são normalmente incluídas.

  1. Defina o buffer do estêncil de profundidade de destino da renderização atual.
  2. Limpe as exibições de estêncil e renderização.
  3. Prepare os sombreadores de vértice e pixel para o desenho.
  4. Renderize os objetos 3D na cena.
  5. Renderize qualquer objeto 2D que você queira exibir na frente da cena.
  6. Apresente a imagem renderizada no monitor.

O método MarbleMaze::Render associa as exibições de estêncil de profundidade e destino de renderização, limpa essas exibições, desenha a cena e, em seguida, desenha a sobreposição.

Preparando os destinos de renderização

Antes de renderizar a sua cena, você deve definir o buffer do estêncil de profundidade de destino da renderização atual. Se não houver garantia de que a cena seja desenhada em cada pixel da tela, limpe também as exibições de renderização e estêncil. O Marble Maze limpa as exibições de renderização e estêncil em cada quadro para garantir que não haja artefatos visíveis do quadro anterior.

O exemplo a seguir mostra como o método MarbleMaze::Render chama o método ID3D11DeviceContext::OMSetRenderTargets para definir o destino de renderização e o buffer de estêncil de profundidade como os atuais. A variável de membro m_renderTargetView, um objeto ID3D11RenderTargetView, e a variável de membro m_depthStencilView, um objeto ID3D11DepthStencilView, são definidas e inicializadas pela classe DirectXBase.


// Bind the render targets.
m_d3dContext->OMSetRenderTargets(
    1,
    m_renderTargetView.GetAddressOf(),
    m_depthStencilView.Get()
    );

// Clear the render target and depth stencil to default values. 
const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };

m_d3dContext->ClearRenderTargetView(
    m_renderTargetView.Get(),
    clearColor
    );

m_d3dContext->ClearDepthStencilView(
    m_depthStencilView.Get(),
    D3D11_CLEAR_DEPTH,
    1.0f,
    0
    );


As interfaces ID3D11RenderTargetView e ID3D11DepthStencilView dão suporte ao mecanismo de exibição de texturas que é fornecido pelo Direct3D 10 e versões posteriores. Para saber mais sobre exibições de textura, veja Exibições de textura (Direct3D 10). O método OMSetRenderTargets prepara o estágio do agente de mesclagem de saída do pipeline Direct3D. Para saber mais sobre o estágio do agente de mesclagem de saída, veja Estágio do agente de mesclagem de saída.

Preparando os sombreadores de vértice e pixel

Antes de renderizar os objetos da cena, realize as etapas a seguir para preparar os sombreadores de vértice e pixel para desenho:

  1. Defina o layout de entrada do sombreador como o layout atual.
  2. Defina os sombreadores de vértice e pixel como os sombreadores atuais.
  3. Atualize qualquer buffer constante com dados que você tenha que transmitir para sombreadores.

Importante  O Marble Maze usa um par de sombreadores de vértice e pixel para todos os objetos 3D. Se o seu jogo usar mais de um par de sombreadores, você deverá realizar essas etapas sempre que desenhar objetos que usam sombreadores diferentes. Para reduzir a sobrecarga associada à mudança do estado do sombreador, convém agrupar as chamadas de renderização para todos os objetos que usam os mesmos sombreadores.

A seção Carregando sombreadores neste documento descreve a forma como o layout de entrada é criado quando o sombreamento de vértice é criado. O exemplo a seguir mostra como o método MarbleMaze::Render usa o método ID3D11DeviceContext::IASetInputLayout para definir esse layout como o layout atual.


m_d3dContext->IASetInputLayout(m_inputLayout.Get());


O exemplo a seguir mostra como o método MarbleMaze::Render usa os métodos ID3D11DeviceContext::VSSetShader e ID3D11DeviceContext::PSSetShader para definir os sombreadores de vértice e de pixel como os sombreadores atuais, respectivamente.


// Set the vertex shader stage state.
m_d3dContext->VSSetShader(
    m_vertexShader.Get(),   // use this vertex shader 
    nullptr,                // don't use shader linkage
    0                       // don't use shader linkage
    );

// Set the pixel shader stage state.
m_d3dContext->PSSetShader(
    m_pixelShader.Get(),    // use this pixel shader 
    nullptr,                // don't use shader linkage
    0                       // don't use shader linkage
    );

m_d3dContext->PSSetSamplers(
    0,                       // starting at the first sampler slot
    1,                       // set one sampler binding
    m_sampler.GetAddressOf() // to use this sampler
    );


Depois que MarbleMaze::Render define os sombreadores e seu layout de entrada, ele usa o método ID3D11DeviceContext::UpdateSubresource para atualizar o buffer constante com o modelo, a exibição e matrizes de projeção do labirinto. O método UpdateSubresource copia os dados de matriz da memória da CPU para a memória da GPU. Lembre-se que os componentes de modelo e exibição da estrutura ConstantBuffer são atualizados no método MarbleMaze::Update. Em seguida, o método MarbleMaze::Render chama os métodos ID3D11DeviceContext::VSSetConstantBuffers e ID3D11DeviceContext::PSSetConstantBuffers para definir esse buffer constante como o atual.


// Update the constant buffer with the new data.
m_d3dContext->UpdateSubresource(
    m_constantBuffer.Get(),
    0,
    nullptr,
    &m_mazeConstantBufferData,
    0,
    0
    );

m_d3dContext->VSSetConstantBuffers(
    0,                // starting at the first constant buffer slot
    1,                // set one constant buffer binding
    m_constantBuffer.GetAddressOf() // to use this buffer
    );

m_d3dContext->PSSetConstantBuffers(
    0,                // starting at the first constant buffer slot
    1,                // set one constant buffer binding
    m_constantBuffer.GetAddressOf() // to use this buffer
    );


O método MarbleMaze::Render realiza etapas semelhantes para preparar a renderização do mármore.

Renderizando o labirinto e a bolinha

Depois de ativar os sombreadores atuais, você pode desenhar os objetos da cena. O método MarbleMaze::Render chama o método SDKMesh::Render para renderizar a malha do labirinto.


m_mazeMesh.Render(m_d3dContext.Get(), 0, INVALID_SAMPLER_SLOT, INVALID_SAMPLER_SLOT);


O método MarbleMaze::Render realiza etapas semelhantes para renderizar a bolinha.

Conforme mencionado anteriormente neste documento, a classe SDKMesh é fornecida para fins de demonstração, mas nós não a recomendamos para uso em um jogo de qualidade de produção. No entanto, observe que o método SDKMesh::RenderMesh, que é chamado por SDKMesh::Render, usa os métodos ID3D11DeviceContext::IASetVertexBuffers e ID3D11DeviceContext::IASetIndexBuffer para definir os buffers de vértice e índice atuais que definem a malha e o método ID3D11DeviceContext::DrawIndexed para desenhar os buffers. Para saber mais sobre como trabalhar com buffers de vértice e de índice, veja Introdução a buffers no Direct3D 11.

Desenhando a interface do usuário e a sobreposição

Depois de desenhar os objetos de cena 3D, o Maze Marble desenha os elementos da interface do usuário 2D que aparecem na frente da cena.

O método MarbleMaze::Render termina desenhando a interface do usuário e a sobreposição.


// Draw the user interface and the overlay.
UserInterface::GetInstance().Render();

m_sampleOverlay->Render();


O método UserInterface::Render usa um objeto ID2D1DeviceContext para desenhar os elementos da interface do usuário. Esse método define o estado do desenho, desenha todos os elementos ativos da interface do usuário e, em seguida, restaura o estado anterior do desenho.


void UserInterface::Render()
{
    m_d2dContext->SaveDrawingState(m_stateBlock.Get());
    m_d2dContext->BeginDraw();
    m_d2dContext->SetTransform(D2D1::Matrix3x2F::Identity());
    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if ((*iter)->IsVisible())
            (*iter)->Render();
    }

    m_d2dContext->EndDraw();
    m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}


O método SampleOverlay::Render usa uma técnica semelhante para desenhar o bitmap da sobreposição.

Apresentando a cena

Depois de desenhar todos os objetos de cena 2D e 3D, o Maze Marble apresenta a imagem renderizada no monitor. Ele sincroniza o desenho com o espaço em branco vertical para garantir que não haja perda de tempo com o desenho de quadros que nunca chegarão a aparecer na tela. O Marble Maze também lida com mudanças de dispositivo quando ele apresenta a cena.

Após o retorno do método MarbleMaze::Render, o loop do jogo chama o método MarbleMaze::Present para enviar a imagem renderizada ao monitor ou à tela. A classe MarbleMaze não substitui o método DirectXBase::Present. O método DirectXBase::Present chama IDXGISwapChain1::Present para realizar a operação presente, conforme mostrado no exemplo a seguir:


// The application may optionally specify "dirty" or "scroll" rects 
// to improve efficiency in certain scenarios. 
// In this sample, however, we do not utilize those features.
DXGI_PRESENT_PARAMETERS parameters = {0};
parameters.DirtyRectsCount = 0;
parameters.pDirtyRects = nullptr;
parameters.pScrollRect = nullptr;
parameters.pScrollOffset = nullptr;

// 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->Present1(1, 0, &parameters);


Neste exemplo, m_swapChain é um objeto IDXGISwapChain1. A inicialização desse objeto está descrita na seção Inicializando o Direct3D e o Direct2D deste documento.

O primeiro parâmetro para IDXGISwapChain1::Present, SyncInterval, especifica o número de espaços em branco verticais que devem ser aguardados antes da apresentação do quadro. O Marble Maze especifica 1 e, portanto, aguarda até o próximo espaço em branco vertical. Um espaço em branco vertical é o intervalo entre o momento em que um quadro termina de ser desenhado no monitor e o início do próximo quadro.

O método IDXGISwapChain1::Present1 retorna um código de erro que indica que o dispositivo foi removido ou falhou de alguma outra forma. Nesse caso, o Marble Maze reinicializa o dispositivo.


// Reinitialize the renderer if the device was disconnected  
// or the driver was upgraded. 
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
    Initialize(m_window, m_dpi);
}
else
{
    DX::ThrowIfFailed(hr);
}


Próximas etapas

Leia Adicionando entrada e interatividade à amostra do Marble Maze para saber mais sobre algumas das principais práticas que você deve ter em mente ao trabalhar com dispositivos de entrada. Esse documento discute como o Marble Maze dá suporte para toque, acelerômetro, controlador do Xbox 360 e entrada com mouse.

Tópicos relacionados

Adicionando entrada e interatividade à amostra do Marble Maze
Estrutura do aplicativo Marble Maze
Desenvolvendo o Marble Maze, um jogo da Windows Store em C++ e DirectX

 

 

Mostrar:
© 2014 Microsoft