Portar o GLSL
APIs importantes
Depois de mover o código que cria e configura seus buffers e objetos de sombreador, é hora de portar o código dentro desses sombreadores da GLSL (GL Shader Language) do OpenGL ES 2.0 para a HLSL (High-level Shader Language) do Direct3D 11.
No OpenGL ES 2.0, os sombreadores retornam dados após a execução usando intrínsecos como gl_Position, gl_FragColor ou gl_FragData[n] (em que n é o índice de um destino de renderização específico). No Direct3D, não há intrínsecos específicos e os sombreadores retornam dados como o tipo de retorno de suas respectivas funções main().
Os dados que você deseja interpolar entre os estágios do sombreador, como a posição do vértice ou normal, são tratados por meio do uso da declaração variável . No entanto, o Direct3D não tem essa declaração; em vez disso, todos os dados que você deseja passar entre os estágios do sombreador devem ser marcados com uma semântica HLSL. A semântica específica escolhida indica a finalidade dos dados e é. Por exemplo, você declararia os dados de vértice que deseja interpolar entre o sombreador de fragmento como:
float4 vertPos : POSITION;
or
float4 vertColor : COLOR;
Onde POSITION é a semântica usada para indicar dados de posição de vértice. POSITION também é um caso especial, pois após a interpolação, ele não pode ser acessado pelo sombreador de pixel. Portanto, você deve especificar a entrada para o sombreador de pixel com SV_POSITION e os dados de vértice interpolados serão colocados nessa variável.
float4 position : SV_POSITION;
A semântica pode ser declarada nos métodos body (main) de shaders. Para sombreadores de pixel, SV_TARGET[n], que indica um destino de renderização, é necessário no método body. (SV_TARGET sem um sufixo numérico é padronizado para renderizar o índice de destino 0.)
Observe também que os sombreadores de vértice são necessários para gerar a semântica do valor do sistema SV_POSITION. Essa semântica resolve os dados de posição do vértice para valores de coordenadas em que x está entre -1 e 1, y está entre -1 e 1, z é dividido pelo valor w da coordenada homogênea original (z/w) e w é 1 dividido pelo valor w original (1/w). Os sombreadores de pixel usam a semântica de valor do sistema SV_POSITION para recuperar o local do pixel na tela, em que x está entre 0 e a largura do destino de renderização e y está entre 0 e a altura do destino de renderização (cada deslocamento em 0,5). Os sombreadores de pixel de nível de recurso 9_x não podem ler o valor SV_POSITION.
Os buffers constantes devem ser declarados com cbuffer e associados a um registro inicial específico para pesquisa.
Direct3D 11: uma declaração de buffer constante HLSL
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mvp;
};
Aqui, o buffer constante usa o registro b0 para manter o buffer empacotado. Todos os registros são referidos no formulário b#. Para obter mais informações sobre a implementação HLSL de buffers constantes, registros e empacotamento de dados, leia Constantes de sombreador (HLSL).
Em nosso exemplo simples do OpenGL ES 2.0, o sombreador de vértice tem três entradas: uma matriz 4x4 de projeção de exibição de modelo constante e dois vetores de 4 coordenadas. Esses dois vetores contêm a posição do vértice e sua cor. O sombreador transforma o vetor de posição em coordenadas de perspectiva e o atribui ao gl_Position intrínseco para rasterização. A cor do vértice também é copiada para uma variável variável para interpolação durante a rasterização.
OpenGL ES 2.0: Sombreador de vértice para o objeto de cubo (GLSL)
uniform mat4 u_mvpMatrix;
attribute vec4 a_position;
attribute vec4 a_color;
varying vec4 destColor;
void main()
{
gl_Position = u_mvpMatrix * a_position;
destColor = a_color;
}
Agora, no Direct3D, a matriz de projeção de exibição de modelo constante está contida em um buffer constante empacotado no registro b0, e a posição e a cor do vértice são marcadas especificamente com a respectiva semântica HLSL apropriada: POSITION e COLOR. Como nosso layout de entrada indica uma disposição específica desses dois valores de vértice, você cria um struct para mantê-los e declarou-o como o tipo para o parâmetro de entrada na função de corpo do sombreador (principal). (Você também pode especificá-los como dois parâmetros individuais, mas isso pode ficar complicado.) Você também especifica um tipo de saída para esse estágio, que contém a posição e a cor interpoladas, e o declara como o valor retornado para a função de corpo do sombreador de vértice.
Direct3D 11: Sombreador de vértice para o objeto de cubo (HLSL)
cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
matrix mvp;
};
// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
float3 pos : POSITION;
float3 color : COLOR;
};
// Per-vertex color data passed through the pixel shader.
struct PixelShaderInput
{
float3 pos : SV_POSITION;
float3 color : COLOR;
};
PixelShaderInput main(VertexShaderInput input)
{
PixelShaderInput output;
float4 pos = float4(input.pos, 1.0f); // add the w-coordinate
pos = mul(mvp, projection);
output.pos = pos;
output.color = input.color;
return output;
}
O tipo de dados de saída, PixelShaderInput, é preenchido durante a rasterização e fornecido ao sombreador de fragmento (pixel).
Nosso exemplo de sombreador de fragmento no GLSL é extremamente simples: forneça o gl_FragColor intrínseco com o valor de cor interpolado. O OpenGL ES 2.0 irá gravá-lo no destino de renderização padrão.
OpenGL ES 2.0: sombreador de fragmento para o objeto de cubo (GLSL)
varying vec4 destColor;
void main()
{
gl_FragColor = destColor;
}
O Direct3D é quase tão simples. A única diferença significativa é que a função body do sombreador de pixel deve retornar um valor. Como a cor é um valor flutuante de 4 coordenadas (RGBA), você indica float4 como o tipo de retorno e, em seguida, especifica o destino de renderização padrão como a semântica do valor do sistema SV_TARGET.
Direct3D 11: sombreador de pixel para o objeto de cubo (HLSL)
struct PixelShaderInput
{
float4 pos : SV_POSITION;
float3 color : COLOR;
};
float4 main(PixelShaderInput input) : SV_TARGET
{
return float4(input.color, 1.0f);
}
A cor do pixel na posição é gravada no destino de renderização. Agora, vamos ver como exibir o conteúdo desse destino de renderização no Draw na tela!
Portar os buffers de vértice e os dados
Entender a semântica HLSL e o empacotamento de buffers constantes pode evitar um pouco de dor de cabeça de depuração, além de fornecer oportunidades de otimização. Se você tiver uma chance, leia HLSL (Sintaxe de Variável), Introdução aos buffers no Direct3D 11 e Como criar um buffer constante. Caso contrário, aqui estão algumas dicas iniciais para ter em mente sobre semântica e buffers constantes:
- Sempre verifique o código de configuração do Direct3D do renderizador para garantir que as estruturas dos buffers constantes correspondam às declarações de struct cbuffer no HLSL e que os tipos escalares do componente correspondam em ambas as declarações.
- No código C++ do renderizador, use tipos DirectXMath em suas declarações de buffer constante para garantir o empacotamento de dados adequado.
- A melhor maneira de usar eficientemente os buffers de constantes é organizar as variáveis de sombreador em buffers de constantes com base na frequência de atualização deles. Por exemplo, se você tiver alguns dados uniformes que são atualizados uma vez por quadro e outros dados uniformes que são atualizados somente quando a câmera se move, considere separar esses dados em dois buffers constantes separados.
- A semântica que você esqueceu de aplicar ou que aplicou incorretamente será sua primeira fonte de erros de compilação de sombreador (FXC). Verifique-os novamente! Os documentos podem ser um pouco confusos, pois muitas páginas e exemplos mais antigos se referem a diferentes versões da semântica HLSL anteriores ao Direct3D 11.
- Certifique-se de saber qual nível de recurso do Direct3D você está direcionando para cada sombreador. A semântica para o nível de recurso 9_* é diferente daquelas para 11_1.
- A semântica SV_POSITION resolve os dados de posição pós-interpolação associados para valores de coordenadas em que x está entre 0 e a largura do destino de renderização, y está entre 0 e a altura do destino de renderização, z é dividido pelo valor w da coordenada homogênea original (z/w) e w é 1 dividido pelo valor w original (1/w).
Como portar um renderizador OpenGL ES 2.0 simples para o Direct3D 11
Portar os objetos de sombreador
Portar os buffers de vértice e os dados