Mapeando texels diretamente para pixels (Direct3D 9)

Ao renderizar a saída 2D usando vértices pré-transformados, deve-se tomar cuidado para garantir que cada área texel corresponda corretamente a uma única área de pixel, caso contrário, pode ocorrer distorção de textura. Ao entender os conceitos básicos do processo que o Direct3D segue ao rasterizar e texturizar triângulos, você pode garantir que seu aplicativo Direct3D renderize corretamente a saída 2D.

ilustração de uma exibição de resolução 6x6

O diagrama anterior mostra pixels modelados como quadrados. Na realidade, no entanto, pixels são ponto, não quadrados. Cada quadrado no diagrama anterior indica a área iluminada pelo pixel, mas um pixel é sempre apenas um ponto no centro de um quadrado. Essa distinção, embora aparentemente pequena, é importante. Uma ilustração melhor da mesma exibição é mostrada no diagrama a seguir.

ilustração de uma exibição que consiste em pixels

O diagrama anterior mostra corretamente cada pixel físico como um ponto no centro de cada célula. A coordenada de espaço na tela (0, 0) está localizada diretamente no pixel superior esquerdo e, portanto, no centro da célula superior esquerda. O canto superior esquerdo da tela está, portanto, em (-0,5, -0,5) porque são 0,5 células à esquerda e 0,5 células acima do pixel superior esquerdo. O Direct3D renderizará um quad com cantos em (0, 0) e (4, 4), conforme mostrado na ilustração a seguir.

ilustração de uma estrutura de tópicos de um quad não inicializado entre (0, 0) e (4, 4)

A ilustração anterior mostra onde o quad matemático está em relação à exibição, mas não mostra como será o quad quando o Direct3D o rasterizar e o enviar para a exibição. Na verdade, é impossível que uma tela de varredura preencha o quadriciclo exatamente como mostrado porque as bordas do quadrante não coincidem com os limites entre as células de pixel. Em outras palavras, como cada pixel só pode exibir uma única cor, cada célula de pixel é preenchida com apenas uma única cor; se a exibição renderizasse o quad exatamente como mostrado, as células de pixel ao longo da borda do quadrante precisariam mostrar duas cores distintas: azul, onde coberto pelo quadriciclo e branco, em que apenas a tela de fundo é visível.

Em vez disso, o hardware gráfico tem a tarefa de determinar quais pixels devem ser preenchidos para aproximar o quad. Esse processo é chamado de rasterização e é detalhado em Regras de Rasterização (Direct3D 9). Para esse caso específico, o quad rasterizado é mostrado na ilustração a seguir.

ilustração de um quad nãotexturado desenhado de (0,0) para (4,4)

Observe que o quad passado para Direct3D tem cantos em (0, 0) e (4, 4), mas a saída rasterizada (a ilustração anterior) tem cantos em (-0,5,-0,5) e (3,5,3,5). Compare as duas ilustrações anteriores para renderizar diferenças. Você pode ver que o que a tela realmente renderiza é o tamanho correto, mas foi deslocado por -0,5 células nas direções x e y. No entanto, exceto para técnicas de várias amostras, essa é a melhor aproximação possível para o quadrante. (Consulte o Exemplo de Suavização para obter uma cobertura completa de várias amostras.) Lembre-se de que, se o rasterizador preenchesse cada célula que o quad cruzou, a área resultante seria da dimensão 5 x 5 em vez do 4 x 4 desejado.

Se você pressupor que as coordenadas da tela se originam no canto superior esquerdo da grade de exibição em vez do pixel superior esquerdo, o quad aparecerá exatamente como esperado. No entanto, a diferença fica clara quando o quadriciclo recebe uma textura. A ilustração a seguir mostra a textura 4 x 4 que você mapeará diretamente para o quadrante.

ilustração de uma textura 4x4

Como a textura é de 4 x 4 texels e o quad é de 4 x 4 pixels, você pode esperar que o quad texturizado apareça exatamente como a textura, independentemente do local na tela onde o quad é desenhado. No entanto, esse não é o caso; até mesmo pequenas alterações na posição influenciam como a textura é exibida. A ilustração a seguir mostra como um quad entre (0, 0) e (4, 4) é exibido após ser rasterizado e texturizado.

ilustração de um quad texturizado desenhado de (0, 0) e (4, 4)

O quad desenhado na ilustração anterior mostra a saída texturizada (com um modo de filtragem linear e um modo de endereçamento de fixação) com a estrutura de tópicos rasterizada sobreposta. O restante deste artigo explica exatamente por que a saída se parece com a textura, mas para quem quer a solução, aqui está: As bordas do quad de entrada precisam estar sobre as linhas de limite entre células de pixel. Ao simplesmente deslocar as coordenadas quad x e y em -0,5 unidades, as células texel cobrirão perfeitamente as células de pixel e o quad pode ser perfeitamente recriado na tela. (A última ilustração neste tópico mostra o quad nas coordenadas corrigidas.)

Os detalhes de por que a saída rasterizada só tem uma leve semelhança com a textura de entrada estão diretamente relacionados à maneira como o Direct3D aborda e amostra texturas. O que se segue pressupõe que você tenha uma boa compreensão do espaço de coordenadas de textura e da filtragem de textura bilinear.

Voltando à nossa investigação da saída de pixel estranha, faz sentido rastrear a cor de saída de volta para o sombreador de pixel: o sombreador de pixel é chamado para cada pixel selecionado para fazer parte da forma rasterizada. O quad azul sólido retratado em uma ilustração anterior poderia ter um sombreador particularmente simples:

float4 SolidBluePS() : COLOR
{ 
    return float4( 0, 0, 1, 1 );
} 

Para o quad texturizado, o sombreador de pixel deve ser alterado ligeiramente:

texture MyTexture;

sampler MySampler = 
sampler_state 
{ 
    Texture = <MyTexture>;
    MinFilter = Linear;
    MagFilter = Linear;
    AddressU = Clamp;
    AddressV = Clamp;
};

float4 TextureLookupPS( float2 vTexCoord : TEXCOORD0 ) : COLOR
{
    return tex2D( MySampler, vTexCoord );
} 

Esse código pressupõe que a textura 4 x 4 esteja armazenada em MyTexture. Conforme mostrado, o sampler de textura MySampler é definido para executar a filtragem bilinear em MyTexture. O sombreador de pixel é chamado uma vez para cada pixel rasterizado e cada vez que a cor retornada é a cor de textura amostrada em vTexCoord. Sempre que o sombreador de pixel é chamado, o argumento vTexCoord é definido como as coordenadas de textura nesse pixel. Isso significa que o sombreador está solicitando ao amostrador de textura a cor da textura filtrada no local exato do pixel, conforme detalhado na ilustração a seguir.

ilustração de locais de amostragem para coordenadas de textura

A textura (mostrada sobreposta) é amostrada diretamente em locais de pixel (mostrados como pontos pretos). As coordenadas de textura não são afetadas pela rasterização (elas permanecem no espaço de tela projetado do quad original). Os pontos pretos mostram onde estão os pixels de rasterização. As coordenadas de textura em cada pixel são facilmente determinadas interpolando as coordenadas armazenadas em cada vértice: o pixel em (0,0) coincide com o vértice em (0, 0); portanto, as coordenadas de textura nesse pixel são simplesmente as coordenadas de textura armazenadas nesse vértice, UV (0,0, 0,0). Para o pixel em (3, 1), as coordenadas interpoladas são UV (0,75, 0,25) porque esse pixel está localizado em três quartos da largura da textura e um quarto de sua altura. Essas coordenadas interpoladas são o que são passadas para o sombreador de pixel.

Os texels não se alinham com os pixels neste exemplo; cada pixel (e, portanto, cada ponto de amostragem) é posicionado no canto de quatro texels. Como o modo de filtragem é definido como Linear, o sampler terá a média das cores dos quatro texels que compartilham esse canto. Isso explica por que o pixel esperado para ser vermelho é, na verdade, três quartos cinza mais um quarto vermelho, o pixel esperado para ser verde é meio cinza mais um quarto vermelho mais um quarto verde, e assim por diante.

Para corrigir esse problema, tudo o que você precisa fazer é mapear corretamente o quad para os pixels para os quais ele será rasterizado e, assim, mapear corretamente os texels para pixels. A ilustração a seguir mostra os resultados do desenho do mesmo quadrante entre (-0,5, -0,5) e (3,5, 3,5), que é o quad pretendido desde o início.

ilustração de um quad texturizado que corresponde ao quad rasterizado

A ilustração anterior demonstra que o quad (mostrado descrito de (-0,5, -0,5) para (3,5, 3,5)) corresponde exatamente à área rasterizada.

Resumo

Em resumo, pixels e texels são, na verdade, pontos, não blocos sólidos. O espaço na tela é originado no pixel superior esquerdo, mas as coordenadas de textura se originam no canto superior esquerdo da grade da textura. Mais importante, lembre-se de subtrair 0,5 unidades dos componentes x e y das posições de vértice ao trabalhar no espaço de tela transformado para alinhar corretamente texels com pixels.

O código a seguir é um exemplo de deslocamento dos vértices de um quadrado de 256 por 256 para exibir corretamente uma textura de 256 por 256 no espaço de tela transformado.

//define FVF with vertex values in transformed screen space
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_TEX1)

struct CUSTOMVERTEX
{
    FLOAT x, y, z, rhw; // position
    FLOAT tu, tv;       // texture coordinates
};

//unadjusted vertex values
float left = 0.0f;
float right = 255.0f;
float top = 0.0f;
float bottom = 255.0f;


//256 by 256 rectangle matching 256 by 256 texture
CUSTOMVERTEX vertices[] =
{
    { left,  top,    0.5f, 1.0f, 0.0f, 0.0f}, // x, y, z, rhw, u, v
    { right, top,    0.5f, 1.0f, 1.0f, 0.0f},
    { right, bottom, 0.5f, 1.0f, 1.0f, 1.0f},
    { left,  top,    0.5f, 1.0f, 0.0f, 0.0f},
    { right, bottom, 0.5f, 1.0f, 1.0f, 1.0f},
    { left,  bottom, 0.5f, 1.0f, 0.0f, 1.0f},
    
};
//adjust all the vertices to correctly line up texels with pixels 
for (int i=0; i<6; i++)
{
    vertices[i].x -= 0.5f;
    vertices[i].y -= 0.5f;
}

Coordenadas de textura