Renderização perfeita de pixel para um ponto de encontro com um quad de tela cheia

9

Estou com problemas para processar um monte de valores para um rendertarget. Os valores nunca acabam no intervalo exato que eu quero. Basicamente, eu uso um quad de tela cheia e um sombreador de pixel para renderizar a textura do meu alvo de rendert e, em seguida, pretendo usar as coordenadas da textura como base para alguns cálculos no sombreador. As coordenadas de textura do quad variam de (0,0) no canto superior esquerdo a (1,1) no canto inferior direito ... o problema é que, após a interpolação, esses valores não chegam ao sombreador de pixels. .

Um exemplo: renderizo para uma textura 4x4 e o sombreador, neste caso, simplesmente gera as coordenadas da textura (u, v) nos canais vermelho e verde:

return float4(texCoord.rg, 0, 1);

O que eu quero tirar disso é uma textura em que o pixel no canto superior esquerdo é RGB (0,0,0) e, portanto, preto, o pixel no canto superior direito é RGB (255,0,0) e, portanto, um brilho vermelho e o pixel no canto inferior esquerdo é RGB (0,255,0) - um verde brilhante.

No entanto, em vez disso, recebo isso aqui à direita:

(renderização em linha reta, sem correção)
renderização direta do quad, sem correção aplicada

O pixel superior esquerdo é preto, mas só recebo um vermelho relativamente escuro e um verde escuro nos outros cantos. Seus valores RGB são (191,0,0) e (0,191,0). Eu suspeito fortemente que isso tenha a ver com os locais de amostragem do quad: o pixel superior esquerdo mostra corretamente o canto superior esquerdo do quad e recebe (0,0) como coordenadas UV, mas os outros pixels do canto não amostram de os outros cantos do quad. Ilustrei isso na imagem à esquerda com a caixa azul representando o quadrilátero e os pontos brancos nas coordenadas de amostragem superiores.

Agora eu sei sobre o deslocamento de meio pixel que você deve aplicar às suas coordenadas ao renderizar quads alinhados à tela no Direct3D9. Vamos ver que tipo de resultado eu recebo disso:

(quad renderizado com deslocamento de meio pixel do DX9)
quad processado com deslocamento de meio pixel

Vermelho e verde ficaram mais brilhantes, mas ainda não estão corretos: 223 é o máximo que recebo no canal de cores vermelho ou verde. Mas agora, eu nem tenho mais preto puro, mas um cinza amarelado escuro com RGB (32,32,0)!

O que eu realmente preciso seria esse tipo de renderização:

(renderização alvo, tamanho reduzido do quad)
pretos puros, verdes e vermelhos com interpolação correta

Parece que eu tenho que mover a borda direita e inferior do meu quad exatamente um pixel para cima e para a esquerda, em comparação com a primeira figura. Em seguida, a coluna direita e a linha inferior de pixels devem obter corretamente as coordenadas UV diretamente da borda do quad:

VertexPositionTexture[] quad = new VertexPositionTexture[]
{
    new VertexPositionTexture(new Vector3(-1.0f,  1.0f, 1f), new Vector2(0,0)),
    new VertexPositionTexture(new Vector3(1.0f - pixelSize.X,  1.0f, 1f), new Vector2(1,0)),
    new VertexPositionTexture(new Vector3(-1.0f, -1.0f + pixelSize.Y, 1f), new Vector2(0,1)),
    new VertexPositionTexture(new Vector3(1.0f - pixelSize.X, -1.0f + pixelSize.Y, 1f), new Vector2(1,1)),
};

No entanto, isso não deu certo e fez com que os pixels inferior e direito não fossem renderizados. Suponho que os centros desses pixels não sejam mais cobertos pelo quad e, portanto, não serão processados ​​pelo shader. Se eu mudar o cálculo do pixelSize em uma quantidade minúscula para aumentar o tamanho do quad, ele meio que funciona ... pelo menos em uma textura 4x4. Ele não funciona em texturas menores e eu temo que distorça sutilmente a distribuição uniforme de valores uv em texturas maiores também:

Vector2 pixelSize = new Vector2((2f / textureWidth) - 0.001f, (2f / textureHeight) - 0.001f);

(Modifiquei o cálculo pixelSize em 0,001f - para texturas menores, por exemplo, tabelas de pesquisa 1D, isso não funciona e preciso aumentá-lo para 0,01f ou algo maior)

É claro que este é um exemplo trivial e eu poderia fazer esse cálculo com muito mais facilidade na CPU sem ter que me preocupar em mapear UVs para centros de pixels ... ainda assim, é preciso haver uma maneira de renderizar um completo e completo [0, 1] alcance de pixels em um rendertarget !?

Mario
fonte
Não que isso ajude, mas estou tendo exatamente o mesmo problema.
IUsedToBeAPygmy
11
Não se esqueça de desativar a amostragem linear também.
R2d2rigo
Não parece que o sistema de coordenadas que você está usando corresponde aos pixels da tela - se fosse, parece que você está desenhando apenas 2 × 2 pixels aqui. A primeira questão que eu resolveria é dimensionar as matrizes do projeto e do modelview, de modo que +1 no espaço de coordenadas seja uma mudança de 1 pixel na tela.
Slipp D. Thompson

Respostas:

4

Seu problema é que os UVs são projetados para serem coordenadas de textura. Uma coordenada de 0,0 é o canto superior esquerdo do pixel superior esquerdo na textura, que não é onde você normalmente deseja ler a textura. Para 2D, você deseja ler a textura no meio desse pixel.

Se minha matemática estiver certa, o que você precisa fazer para solucionar o problema é:

return float4((texCoord.rg - (0.5f/textureSize)) * (textureSize/(textureSize-1)), 0, 1);

Ou seja, você subtrai o deslocamento apropriado para obter o pixel superior esquerdo em 0,0 e, em seguida, aplica uma escala para corrigir o canto inferior direito.

A mesma coisa também pode ser alcançada expandindo as coordenadas UV para a geometria fora da faixa 0-1.

Adão
fonte
Para elaborar a última frase: você pode aplicar essa transformação aos UVs no buffer de vértice para os quatro cantos do quad e, em seguida, no pixel shader return float4(texCoord.rg, 0, 1);.
Nathan Reed
Eu tentei a fórmula sugerida acima e ela não funcionou: na minha amostra 4x4 de segmentação acima, recebo 0, 42, 127 e 212 nos canais R e G, da esquerda para a direita ou de cima para baixo. No entanto, eu gostaria de valores de 0 a 255 em etapas espaçadas igualmente (para uma textura 4x4 que seria 0, 85, 170 e 255). Também tentei alterar as coordenadas UV, mas ainda não encontrei o deslocamento certo.
Mario
0

Parece que eu tenho que mover a borda direita e inferior do meu quad exatamente um pixel para cima e para a esquerda, em comparação com a primeira figura.

Quase, mas apenas metade do pixel. Apenas subtraia 0,5 dos seus vértices quad. Por exemplo, você quer quad de 256x256 com a mesma textura, eis os seus pontos:

p1 = (-0.5, -0.5)
p2 = (-0.5, 255-0.5)
p3 = (255-0.5, 255-0.5)
p4 = (255-0.5, -0.5)

Ou então, você pode adicionar metade do texel no pixel shader (texCoord.rg + 0.5 * (1.0 / texSize))

O deslocamento de meio pixel é um fato conhecido no DX9. Este e este artigo podem ajudá-lo também.

alariq
fonte
0

Isso certamente está sendo causado por amostragem linear. Se você olhar para os texels à direita e abaixo do pixel superior esquerdo em preto, verá que eles têm 63 nos canais R e / ou G e 2 deles têm 2 em B. Agora, veja amarelo-lamacento-escuro você recebe; são 31 em R e G e 1 em B. Isso é definitivamente o resultado da média de texels em um grupo 2x2; portanto, a solução é definir seu filtro de textura para apontar.

Maximus Minimus
fonte
-2

Isso é simplesmente porque você está usando o texCoords da saída de vértice, que é interpolada pelo disco rígido após o processamento do sombreador de vértice.

Jason Huang
fonte