Introdução à Fase de Saída de Fluxo
Esta seção descreve como usar um sombreador de geometria com a fase de saída de fluxo.
Compilar um Sombreador de Geometria
Esse sombreador de geometria (GS) calcula uma face normal para cada triângulo e emite dados de posição, normal e de coordenada de textura.
struct GSPS_INPUT
{
float4 Pos : SV_POSITION;
float3 Norm : TEXCOORD0;
float2 Tex : TEXCOORD1;
};
[maxvertexcount(12)]
void GS( triangle GSPS_INPUT input[3], inout TriangleStream<GSPS_INPUT> TriStream )
{
GSPS_INPUT output;
//
// Calculate the face normal
//
float3 faceEdgeA = input[1].Pos - input[0].Pos;
float3 faceEdgeB = input[2].Pos - input[0].Pos;
float3 faceNormal = normalize( cross(faceEdgeA, faceEdgeB) );
float3 ExplodeAmt = faceNormal*Explode;
//
// Calculate the face center
//
float3 centerPos = (input[0].Pos.xyz + input[1].Pos.xyz + input[2].Pos.xyz)/3.0;
float2 centerTex = (input[0].Tex + input[1].Tex + input[2].Tex)/3.0;
centerPos += faceNormal*Explode;
//
// Output the pyramid
//
for( int i=0; i<3; i++ )
{
output.Pos = input[i].Pos + float4(ExplodeAmt,0);
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
output.Norm = input[i].Norm;
output.Tex = input[i].Tex;
TriStream.Append( output );
int iNext = (i+1)%3;
output.Pos = input[iNext].Pos + float4(ExplodeAmt,0);
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
output.Norm = input[iNext].Norm;
output.Tex = input[iNext].Tex;
TriStream.Append( output );
output.Pos = float4(centerPos,1) + float4(ExplodeAmt,0);
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
output.Norm = faceNormal;
output.Tex = centerTex;
TriStream.Append( output );
TriStream.RestartStrip();
}
for( int i=2; i>=0; i-- )
{
output.Pos = input[i].Pos + float4(ExplodeAmt,0);
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
output.Norm = -input[i].Norm;
output.Tex = input[i].Tex;
TriStream.Append( output );
}
TriStream.RestartStrip();
}
Tendo esse código em mente, considere que um sombreador de geometria se parece muito com um sombreador de vértice ou de pixel, mas com as seguintes exceções: o tipo retornado pela função, as declarações do parâmetro de entrada e a função intrínseca.
Item | Descrição | |
---|---|---|
Tipo de retorno da função |
O tipo de retorno da função faz uma coisa: declara o número máximo de vértices que podem ser emitidos pelo sombreador. Nesse caso,
define que a saída será de no máximo 12 vértices. |
|
Declarações de parâmetros de entrada |
Essa função recebe dois parâmetros de entrada:
O primeiro parâmetro é uma matriz de vértices (3, nesse caso) definida por uma estrutura GSPS_INPUT (que define dados por vértice como uma posição, uma normal e uma coordenada de textura). O primeiro parâmetro também utiliza a palavra-chave triângulo, o que significa que a fase do assembler de entrada deve emitir dados para o sombreador de geometria como um dos tipos primitivos de triângulo (lista de triângulos ou faixa de triângulos). O segundo parâmetro é um fluxo de triângulos definido pelo tipo TriangleStream<GSPS_INPUT>. Isso significa que o parâmetro é uma matriz de triângulos, cada um dos quais deve ser composto de três vértices (que contêm os dados dos membros do GSPS_INPUT). Use as palavras-chave triângulo e trianglestream para identificar triângulos individuais ou um fluxo de triângulos em um GS. |
|
Função intrínseca |
As linhas de código na função do sombreador utilizam funções intrínsecas de HLSL do núcleo do sombreador comum, exceto as duas últimas linhas, que chamam Append e RestartStrip. Essas funções estão disponíveis apenas para um sombreador de geometria. Acrescentar informa ao sombreador de geometria para acrescentar a saída à faixa atual; RestartStrip cria uma nova faixa primitiva. Uma nova faixa é criada implicitamente em cada invocação da fase GS. |
O restante do sombreador é muito semelhante a um sombreador de vértice ou de pixel. O sombreador de geometria utiliza uma estrutura para declarar os parâmetros de entrada e marca o membro posição com a semântica SV_POSITION para informar ao hardware que se trata de dados posicionais. A estrutura de entrada identifica os outros dois parâmetros de entrada como coordenadas de textura (mesmo que um deles contenha uma face normal). Você pode utilizar sua própria semântica personalizada para a face normal, se preferir.
Depois de projetar o sombreador de geometria, chame D3DCompile para compilar, conforme mostrado no exemplo de código a seguir.
DWORD dwShaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
ID3DBlob** ppShader;
D3DCompile( pSrcData, sizeof( pSrcData ),
"Tutorial13.fx", NULL, NULL, "GS", "gs_4_0",
dwShaderFlags, 0, &ppShader, NULL );
Assim como os sombreadores de vértice e de pixel, você precisa de um sinalizador de sombreador para informar ao compilador como deseja que o sombreador seja compilado (para depuração, otimizado para velocidade etc.), a função do ponto de entrada e o modelo de sombreador a ser validado. Este exemplo cria um sombreador de geometria criado a partir do arquivo Tutorial13.fx, utilizando a função GS. O sombreador é compilado para o modelo de sombreador 4.0.
Criar um objeto Geometry-Shader com saída de fluxo
Uma vez que você saiba que transmitirá os dados da geometria e que tenha compilado o sombreador com êxito, a próxima etapa é chamar ID3D11Device::CreateGeometryShaderWithStreamOutput para criar o objeto do sombreador de geometria.
Mas, primeiro, você precisa declarar a assinatura de entrada da fase de saída de fluxo (SO). Essa assinatura corresponde ou valida as saídas GS e as entradas SO no momento da criação do objeto. O código a seguir é um exemplo da declaração SO.
D3D11_SO_DECLARATION_ENTRY pDecl[] =
{
// semantic name, semantic index, start component, component count, output slot
{ "SV_POSITION", 0, 0, 4, 0 }, // output all components of position
{ "TEXCOORD0", 0, 0, 3, 0 }, // output the first 3 of the normal
{ "TEXCOORD1", 0, 0, 2, 0 }, // output the first 2 texture coordinates
};
D3D11Device->CreateGeometryShaderWithStreamOut( pShaderBytecode, ShaderBytecodesize, pDecl,
sizeof(pDecl), NULL, 0, 0, NULL, &pStreamOutGS );
Essa função recebe vários parâmetros, incluindo:
- Um ponteiro para o sombreador de geometria compilado (ou sombreador de vértice, se nenhum sombreador de geometria estiver presente e os dados forem transmitidos diretamente do sombreador de vértice). Para obter informações sobre como obter esse ponteiro, confira Obtendo um Ponteiro para um Sombreador compilado.
- Um ponteiro para uma matriz de declarações que descrevem os dados de entrada para a fase de saída do fluxo. (Confira D3D11_SO_DECLARATION_ENTRY.) Você pode fornecer até 64 declarações, uma para cada tipo diferente de elemento que será a saída da fase SO. A matriz de entradas de declaração descreve o layout dos dados, independentemente do fato de que será associado a um único buffer ou a vários buffers para a saída do fluxo.
- O número de elementos que são gravados pela fase SO.
- Um ponteiro para o objeto do sombreador de geometria criado (consulte ID3D11GeometryShader).
Nessa situação, o stride do buffer é NULO, o índice do fluxo que será enviado ao rasterizador é 0 e a interface de vinculação da classes é NULA.
A declaração de saída de fluxo define a forma como os dados são gravados em um recurso de buffer. Você pode adicionar quantos componentes desejar à declaração de saída. Use a fase SO para gravar em um único recurso de buffer ou em vários recursos de buffer. Em um único buffer, a fase SO pode gravar muitos elementos diferentes por vértice. Para vários buffers, a fase SO só pode gravar um único elemento de dados por vértice em cada buffer.
Para usar a fase SO sem utilizar um sombreador de geometria, chame ID3D11Device::CreateGeometryShaderWithStreamOutput e passe um ponteiro para um sombreador de vértice para o parâmetro pShaderBytecode.
Definir os Destinos de Saída
A última etapa é definir os buffers da fase SO. Os dados podem ser transmitidos em um ou mais buffers na memória para serem utilizados posteriormente. O código a seguir mostra como criar um único buffer que pode ser utilizado para dados de vértice, bem como para o fluxo de dados da fase SO.
ID3D11Buffer *m_pBuffer;
int m_nBufferSize = 1000000;
D3D11_BUFFER_DESC bufferDesc =
{
m_nBufferSize,
D3D11_USAGE_DEFAULT,
D3D11_BIND_STREAM_OUTPUT,
0,
0,
0
};
D3D11Device->CreateBuffer( &bufferDesc, NULL, &m_pBuffer );
Criar um buffer chamando ID3D11Device::CreateBuffer. Este exemplo ilustra o uso padrão, típico de um recurso de buffer que será atualizado com bastante frequência pela CPU. O sinalizador de associação identifica a fase do pipeline à qual o recurso pode ser associado. Qualquer recurso utilizado pela fase SO também deve ser criado com o sinalizador de associação D3D10_BIND_STREAM_OUTPUT.
Uma vez que o buffer tenha sido criado com êxito, defina-o como o dispositivo atual chamando ID3D11DeviceContext::SOSetTargets:
UINT offset[1] = 0;
D3D11Device->SOSetTargets( 1, &m_pBuffer, offset );
Essa chamada recebe o número de buffers, um ponteiro para os buffers e uma matriz de deslocamentos (um deslocamento em cada um dos buffers que indica em que ponto começar a gravar os dados). Os dados serão gravados nesses buffers de saída de streaming quando uma função de desenho for chamada. Uma variável interna mantém o controle da posição em que os dados começarão a ser gravados nos buffers de saída do streaming, e essa variável continuará a ser incrementada até que SOSetTargets seja chamada novamente e um novo valor deslocado seja especificado.
Todos os dados gravados nos buffers de destino serão valores de 32 bits.