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);
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).
fonte
Respostas:
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.
fonte