Compartilhar via


Portar os objetos de sombreador

APIs importantes

Ao portar o renderizador simples do OpenGL ES 2.0, a primeira etapa é configurar os objetos de sombreador de vértice e fragmento equivalentes no Direct3D 11 e garantir que o programa principal possa se comunicar com os objetos de sombreador depois que eles forem compilados.

Observação

Você criou um novo projeto Direct3D? Caso contrário, siga as instruções em Criar um novo projeto DirectX 11 para a Plataforma Universal do Windows (UWP). Este passo a passo pressupõe que você tenha criado os recursos DXGI e Direct3D para desenhar na tela e que são fornecidos no modelo.

Assim como o OpenGL ES 2.0, os sombreadores compilados no Direct3D devem ser associados a um contexto de desenho. No entanto, o Direct3D não tem o conceito de um objeto de programa de sombreador em si; em vez disso, você deve atribuir os sombreadores diretamente a um ID3D11DeviceContext. Esta etapa segue o processo do OpenGL ES 2.0 para criar e associar objetos de sombreador e fornece os comportamentos de API correspondentes no Direct3D.

Instruções

Etapa 1: Compilar os sombreadores

Neste exemplo simples do OpenGL ES 2.0, os sombreadores são armazenados como arquivos de texto e carregados como dados de cadeia de caracteres para compilação em tempo de execução.

OpenGL ES 2.0: Compilar um sombreador

GLuint __cdecl CompileShader (GLenum shaderType, const char *shaderSrcStr)
// shaderType can be GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. Returns 0 if compilation fails.
{
  GLuint shaderHandle;
  GLint compiledShaderHandle;
   
  // Create an empty shader object.
  shaderHandle = glCreateShader(shaderType);

  if (shaderHandle == 0)
  return 0;

  // Load the GLSL shader source as a string value. You could obtain it from
  // from reading a text file or hardcoded.
  glShaderSource(shaderHandle, 1, &shaderSrcStr, NULL);
   
  // Compile the shader.
  glCompileShader(shaderHandle);

  // Check the compile status
  glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compiledShaderHandle);

  if (!compiledShaderHandle) // error in compilation occurred
  {
    // Handle any errors here.
              
    glDeleteShader(shaderHandle);
    return 0;
  }

  return shaderHandle;

}

No Direct3D, os sombreadores não são compilados durante o tempo de execução; eles são sempre compilados em arquivos CSO quando o resto do programa é compilado. Quando você compila seu aplicativo com o Microsoft Visual Studio, os arquivos HLSL são compilados em arquivos CSO (.cso) que seu aplicativo deve carregar. Certifique-se de incluir esses arquivos CSO com seu aplicativo ao empacotá-lo!

Observação O exemplo a seguir executa o carregamento e a compilação do sombreador de forma assíncrona usando a palavra-chave auto e a sintaxe lambda. ReadDataAsync() é um método implementado para o modelo que lê em um arquivo CSO como uma matriz de dados de bytes (fileData).

 

Direct3D 11: compilar um sombreador

auto loadVSTask = DX::ReadDataAsync(m_projectDir + "SimpleVertexShader.cso");
auto loadPSTask = DX::ReadDataAsync(m_projectDir + "SimplePixelShader.cso");

auto createVSTask = loadVSTask.then([this](Platform::Array<byte>^ fileData) {

m_d3dDevice->CreateVertexShader(
  fileData->Data,
  fileData->Length,
  nullptr,
  &m_vertexShader);

auto createPSTask = loadPSTask.then([this](Platform::Array<byte>^ fileData) {
  m_d3dDevice->CreatePixelShader(
    fileData->Data,
    fileData->Length,
    nullptr,
    &m_pixelShader;
};

Etapa 2: Criar e carregar os sombreadores de vértice e fragmento (pixel)

O OpenGL ES 2.0 tem a noção de um "programa" de shader, que serve como interface entre o programa principal em execução na CPU e os shaders, que são executados na GPU. Os sombreadores são compilados (ou carregados de fontes compiladas) e associados a um programa, o que permite a execução na GPU.

OpenGL ES 2.0: Carregando os sombreadores de vértice e fragmento em um programa de sombreamento

GLuint __cdecl LoadShaderProgram (const char *vertShaderSrcStr, const char *fragShaderSrcStr)
{
  GLuint programObject, vertexShaderHandle, fragmentShaderHandle;
  GLint linkStatusCode;

  // Load the vertex shader and compile it to an internal executable format.
  vertexShaderHandle = CompileShader(GL_VERTEX_SHADER, vertShaderSrcStr);
  if (vertexShaderHandle == 0)
  {
    glDeleteShader(vertexShaderHandle);
    return 0;
  }

   // Load the fragment/pixel shader and compile it to an internal executable format.
  fragmentShaderHandle = CompileShader(GL_FRAGMENT_SHADER, fragShaderSrcStr);
  if (fragmentShaderHandle == 0)
  {
    glDeleteShader(fragmentShaderHandle);
    return 0;
  }

  // Create the program object proper.
  programObject = glCreateProgram();
   
  if (programObject == 0)    return 0;

  // Attach the compiled shaders
  glAttachShader(programObject, vertexShaderHandle);
  glAttachShader(programObject, fragmentShaderHandle);

  // Compile the shaders into binary executables in memory and link them to the program object..
  glLinkProgram(programObject);

  // Check the project object link status and determine if the program is available.
  glGetProgramiv(programObject, GL_LINK_STATUS, &linkStatusCode);

  if (!linkStatusCode) // if link status <> 0
  {
    // Linking failed; delete the program object and return a failure code (0).

    glDeleteProgram (programObject);
    return 0;
  }

  // Deallocate the unused shader resources. The actual executables are part of the program object.
  glDeleteShader(vertexShaderHandle);
  glDeleteShader(fragmentShaderHandle);

  return programObject;
}

// ...

glUseProgram(renderer->programObject);

O Direct3D não tem o conceito de um objeto de programa de sombreador. Em vez disso, os sombreadores são criados quando um dos métodos de criação de sombreador na interface ID3D11Device (como ID3D11Device::CreateVertexShader ou ID3D11Device::CreatePixelShader) é chamado. Para definir os sombreadores para o contexto de desenho atual, nós os fornecemos ao ID3D11DeviceContext correspondente com um método de sombreador definido, como ID3D11DeviceContext::VSSetShader para o sombreador de vértice ou ID3D11DeviceContext::P SSetShader para o sombreador de fragmento.

Direct3D 11: defina os sombreadores para o contexto de desenho do dispositivo gráfico.

m_d3dContext->VSSetShader(
  m_vertexShader.Get(),
  nullptr,
  0);

m_d3dContext->PSSetShader(
  m_pixelShader.Get(),
  nullptr,
  0);

Etapa 3: Definir os dados a serem fornecidos aos sombreadores

Em nosso exemplo do OpenGL ES 2.0, temos um uniforme a ser declarado para o pipeline do sombreador:

  • u_mvpMatrix: uma matriz 4x4 de floats que representa a matriz de transformação model-view-projection final que pega as coordenadas do modelo para o cubo e as transforma em coordenadas de projeção 2D para conversão de varredura.

E dois valores de atributo para os dados de vértice:

  • a_position: um vetor de 4 flutuadores para as coordenadas do modelo de um vértice.
  • a_color: Um vetor de 4 flutuadores para o valor de cor RGBA associado ao vértice.

Open GL ES 2.0: Definições GLSL para uniformes e atributos

uniform mat4 u_mvpMatrix;
attribute vec4 a_position;
attribute vec4 a_color;

As variáveis principais do programa correspondentes são definidas como campos no objeto renderizador, neste caso. (Consulte o cabeçalho em Como portar um renderizador OpenGL ES 2.0 simples para o Direct3D 11.) Depois de fazer isso, precisamos especificar os locais na memória em que o programa principal fornecerá esses valores para o pipeline do sombreador, o que normalmente fazemos logo antes de uma chamada de desenho:

OpenGL ES 2.0: Marcando o local dos dados de uniforme e atributo


// Inform the shader of the attribute locations
loc = glGetAttribLocation(renderer->programObject, "a_position");
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, 
    sizeof(Vertex), 0);
glEnableVertexAttribArray(loc);

loc = glGetAttribLocation(renderer->programObject, "a_color");
glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE, 
    sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));
glEnableVertexAttribArray(loc);


// Inform the shader program of the uniform location
renderer->mvpLoc = glGetUniformLocation(renderer->programObject, "u_mvpMatrix");

O Direct3D não tem o conceito de "atributo" ou "uniforme" no mesmo sentido (ou, pelo menos, não compartilha dessa sintaxe). Em vez disso, ele tem buffers constantes, representados como sub-recursos do Direct3D – recursos que são compartilhados entre o programa principal e os programas de sombreador. Alguns desses sub-recursos, como posições e cores de vértices, são descritos como semântica HLSL. Para obter mais informações sobre buffers constantes e semântica HLSL relacionados aos conceitos do OpenGL ES 2.0, leia Objetos, uniformes e atributos de buffer de quadro de porta.

Ao mover esse processo para o Direct3D, convertemos o uniforme em um buffer constante do Direct3D (cbuffer) e atribuímos a ele um registro para pesquisa com a semântica HLSL do registro . Os dois atributos de vértice são tratados como elementos de entrada para os estágios do pipeline do sombreador e também recebem semântica HLSL (POSITION e COLOR0) que informam os sombreadores. O sombreador de pixel usa um SV_POSITION, com o prefixo SV_ indicando que é um valor do sistema gerado pela GPU. (Nesse caso, é uma posição de pixel gerada durante a conversão de digitalização.) VertexShaderInput e PixelShaderInput não são declarados como buffers constantes porque o primeiro será usado para definir o buffer de vértice (consulte Porta dos buffers de vértice e dados) e os dados para o último são gerados como resultado de um estágio anterior no pipeline, que nesse caso é o sombreador de vértice.

Direct3D: definições HLSL para os buffers constantes e dados de vértice

cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
  matrix mvp;
};

// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
  float4 pos : POSITION;
  float4 color : COLOR0;
};

// Per-vertex color data passed through the pixel shader.
struct PixelShaderInput
{
  float4 pos : SV_POSITION;
  float3 color : COLOR0;
};

Para obter mais informações sobre a portabilidade para buffers constantes e a aplicação da semântica HLSL, leia Objetos, uniformes e atributos de buffer de quadro de porta.

Aqui estão as estruturas para o layout dos dados passados para o pipeline do sombreador com um buffer constante ou de vértice.

Direct3D 11: Declarando o layout de buffers de constante e vértice

// Constant buffer used to send MVP matrices to the vertex shader.
struct ModelViewProjectionConstantBuffer
{
  DirectX::XMFLOAT4X4 modelViewProjection;
};

// Used to send per-vertex data to the vertex shader.
struct VertexPositionColor
{
  DirectX::XMFLOAT4 pos;
  DirectX::XMFLOAT4 color;
};

Use os tipos DirectXMath XM* para seus elementos de buffer constantes, pois eles fornecem empacotamento e alinhamento adequados para o conteúdo quando eles são enviados para o pipeline do sombreador. Se você usar tipos e matrizes flutuantes da plataforma Windows padrão, deverá executar o empacotamento e o alinhamento por conta própria.

Para associar um buffer constante, crie uma descrição de layout como uma estrutura CD3D11_BUFFER_DESC e passe-a para ID3DDevice::CreateBuffer. Em seguida, no método de renderização, passe o buffer constante para ID3D11DeviceContext::UpdateSubresource antes de desenhar.

Direct3D 11: Associar o buffer constante

CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);

m_d3dDevice->CreateBuffer(
  &constantBufferDesc,
  nullptr,
  &m_constantBuffer);

// ...

// Only update shader resources that have changed since the last frame.
m_d3dContext->UpdateSubresource(
  m_constantBuffer.Get(),
  0,
  NULL,
  &m_constantBufferData,
  0,
  0);

O buffer de vértice é criado e atualizado de forma semelhante e é discutido na próxima etapa, Portar os buffers de vértice e os dados.

Próxima etapa

Portar os buffers de vértice e os dados

Como portar um renderizador OpenGL ES 2.0 simples para o Direct3D 11

Portar os buffers de vértice e os dados

Portar o GLSL

Desenhar na tela