Interpolação de profundidade para buffer z, com linha de varredura

10

Eu tenho que escrever meu próprio software rasterizer 3d, e até agora eu sou capaz de projetar meu modelo 3d feito de triângulos no espaço 2D:

Giro, traduzo e projeto meus pontos para obter uma representação espacial em 2D de cada triângulo. Então, pego os 3 pontos do triângulo e implemento o algoritmo da linha de varredura (usando interpolação linear) para encontrar todos os pontos [x] [y] ao longo das bordas (esquerda e direita) dos triângulos, para que eu possa varrer o triângulo horizontalmente, linha por linha e preencha com pixels.

Isso funciona. Exceto que eu também tenho que implementar o z-buffering. Isso significa que, conhecendo as coordenadas z giradas e traduzidas dos 3 vértices do triângulo, devo interpolar a coordenada z para todos os outros pontos que encontro com o algoritmo da linha de varredura.

O conceito parece claro o suficiente, primeiro encontro Za e Zb com estes cálculos:

var Z_Slope = (bottom_point_z - top_point_z) / (bottom_point_y - top_point_y);
var Za = top_point_z + ((current_point_y - top_point_y) * Z_Slope);

Então, para cada Zp, faço a mesma interpolação horizontalmente:

var Z_Slope = (right_z - left_z) / (right_x - left_x);
var Zp = left_z + ((current_point_x - left_x) * Z_Slope);

insira a descrição da imagem aqui

E se z atual estiver mais próximo do visualizador do que o z anterior nesse índice, ENTÃO escreva a cor no buffer de cores E escreva o novo z no buffer de z. (meu sistema de coordenadas é x: esquerda -> direita; y: superior -> inferior; z: seu rosto -> tela do computador;)

O problema é que isso dá errado. O projeto está aqui e, se você selecionar o botão de opção "Z-Buffered", verá os resultados ... ( observe que eu uso o algoritmo do pintor (apenas para desenhar o wireframe) no modo "Z-Buffered" para fins de depuração )

PS: Eu li aqui que você deve transformar os z em seus recíprocos (significado z = 1/z) antes de interpolar. Eu tentei isso, e parece que não há mudanças. o que estou perdendo? (alguém poderia esclarecer, precisamente onde você deve transformar z em 1 / z e onde (se) para transformá-lo de volta?)

[EDIT] Aqui estão alguns dados sobre quais valores z máximo e mínimo eu recebo:

    max z: 1;                  min z: -1;                 //<-- obvious, original z of the vertices of the triangles
    max z: 7.197753398761272;  min z: 3.791703256899924;   //<-- z of the points that were drawn to screen (you know, after rotation, translation), by the scanline with zbuffer, gotten with interpolation but not 1/z.
    max z: 0.2649908532179404; min z: 0.13849507306889008;//<-- same as above except I interpolated 1/z instead of z.
//yes, I am aware that changing z to 1/z means flipping the comparison in the zBuffer check. otherwise nothing gets drawn.

Antes de entrar em uma depuração cuidadosa, alguém pode confirmar que meu conceito até agora está correto?

[EDIT2]

Eu resolvi o buffer z. Acontece que a ordem do desenho não foi alterada. As coordenadas z estavam sendo calculadas corretamente.

O problema era que, na tentativa de aumentar minha taxa de quadros, eu estava desenhando caixas 4px / 4px, a cada 4 pixels, em vez de pixels reais na tela. Então, eu estava desenhando 16px por pixel, mas verificando o buffer z para apenas um deles. Eu sou uma besteira.

TL / DR: A questão ainda permanece: Como / por que / quando você precisa usar o recíproco de Z (como em 1 / z) em vez de Z? Porque agora, tudo funciona de qualquer maneira. (não há diferença perceptível).

Spectraljump
fonte
Re: "E é claro que adiciono ao zBuffer, se o z atual estiver mais próximo do visualizador do que o valor anterior nesse índice." Não está claro para mim que você disse isso como queria, mas queria ter certeza de que o que queria dizer era "se z atual está mais próximo do espectador que o z anterior naquele índice ENTÃO, escreva a cor no buffer de cores E escreva o novo z no buffer z "O objetivo do buffer z é bloquear as gravações em cores se uma cor nesse pixel já estiver gravada mais próxima do olho da câmera.
Alturis 4/10/12
Está correto. Desculpe, já era tarde quando redigi minha pergunta. Vou revisar.
Spectraljump #

Respostas:

5

Resposta rápida: Z não é uma função linear de (X ', Y'), mas 1 / Z é. Como você interpola linearmente, obtém resultados corretos para 1 / Z, mas não para Z.

Você não percebe porque, enquanto a comparação entre Z1 e Z2 estiver correta, o zbuffer fará a coisa certa, mesmo que ambos os valores estejam errados. Você definitivamente notará quando adicionar o mapeamento de textura (e para responder à pergunta que você terá então: interpole 1 / Z, U / Z e V / Z e reconstrua U e V a partir desses valores: U = (U / Z) / (1 / Z), V = (V / Z) / (1 / Z). Você me agradecerá mais tarde)

Um exemplo. Pegue um pedaço de papel. Vista de cima para baixo, então esqueça a coordenada Y. X é o eixo horizontal, Z é o eixo vertical, a câmera está em (0, 0), o plano de projeção é z = 1.

Considere os pontos A (-2, 2) e B (2, 4). O ponto médio M do segmento AB é (0, 3). Por enquanto, tudo bem.

Você projeta A em A ': X' = X / Z = -1, então A 'é (-1, 1). Da mesma forma, B 'é (0,5, 1). Mas observe que a projeção de M é (0, 1), que NÃO é o ponto médio de A'B '. Por quê? Como a metade direita do segmento está mais distante da câmera que a metade esquerda, ela fica menor.

Então, o que acontece se você tentar calcular o Z de M 'usando interpolação linear? dx = (0,5 - -1) = 1,5, dz = (4-2) = 2, portanto, para M 'onde X' = 0, o Z linearmente interpolado é zA + (dz / dx) (x - xA) = 2 + (2 / 1,5) (0 - -1) = 2 + 1,333 = 3,3333 - NÃO 3!

Por quê? Porque para cada passo na direção X ', você não move a mesma quantidade na direção Z (ou, em outras palavras, Z não é uma função linear de X'). Por quê? Como quanto mais você dá certo, mais distante o segmento fica da câmera, então um pixel representa uma distância maior no espaço.

Finalmente, o que acontece se você interpolar 1 / Z? Primeiro você calcula 1 / Z em A e B: 0,5 e 0,25, respectivamente. Então você interpola: dx = (0,5 - -1) = 1,5, dz = (0,25 - 0,5) = -0,25, então em X '= 0 você calcula 1 / Z = 0,5 + (-0,25 / 1,5) * (0 - -1) = 0,3333. Mas isso é 1 / Z, então o valor de Z é ... exatamente, 3. Como deveria ser.

Sim, a matemática é incrível.

ggambett
fonte
1
Ah, e em relação a "quando": calcule os valores de 1 / Z antes de começar a rasterizar o triângulo (por exemplo, logo antes do loop vertical), para que você seja interpolado 1 / Z à esquerda e à direita da linha de varredura. Interpole-os linearmente (NÃO faça 1 / Z novamente - os valores interpolados já são 1 / Z!) E desfaça a transformação imediatamente antes de verificar o zbuffer.
precisa saber é o seguinte
1
E finalmente, por que. Um plano (onde o triângulo está embutido) é Ax + By + Cz + D = 0. z é claramente uma função linear de (x, y). Você projeta então x '= x / z e y' = y / z. A partir daí, x = x'z e y = y'z. Se você substituí-los na equação original, obtém Ax'z + By'x + Cz + D = 0. Agora z = -D / (Ax '+ By' + C), onde fica claro que z não é uma função linear de (x ', y'). Mas 1 / z é, portanto, (Ax '+ Por' + C) / -D, que é uma função linear de (x ', y').
precisa saber é o seguinte
1
Você sabe, eu li muitos artigos e cursos, e nenhum deles foi tão claro quanto a sua resposta. Para a posteridade, também observarei que "As letras" U "e" V "denotam os eixos da textura 2D porque" X "," Y "e" Z "já são usados ​​para denotar os eixos do objeto 3D no modelo A texturização UV permite que polígonos que compõem um objeto 3D sejam pintados com cores de uma imagem ". - Wikipedia - Mapeamento UV
Spectraljump
Fico feliz em ouvir isso. Eu fiz, na verdade, ensinar computação gráfica em uma vida anterior :)
ggambett
Muito obrigado - sempre fiquei curioso sobre isso - e não sei se alguma vez encontrei uma resposta melhor! +1
Codesmith