Uma lente grande angular não deve se comportar de maneira diferente de outros modelos comuns. Eles apenas têm um FOV maior (no D3DXMatrixPerspectiveFovLH
sentido - suponho que você use o DirectX) ou valores maiores para a esquerda / direita e inferior / superior (no glFrustum
sentido OpenGL ).
Eu acredito que a parte realmente interessante está na modelagem da lente olho de peixe. Há o Fisheye Quake que você pode estudar, ele vem com a fonte.
A verdadeira projeção olho de peixe
A projeção de uma lente olho de peixe, no entanto, é altamente não linear. No tipo de lente mais comum (que eu saiba, que é limitado às câmeras de vigilância), um ponto M
no espaço é projetado na superfície de um hemisfério unitário e, em seguida, essa superfície sofre uma projeção paralela no disco unitário:
M
x M: world position
\ M': projection of M on the unit hemisphere
\ ______ M": projection of M' on the unit disc (= the screen)
M'_x' `-.
,' |\ `.
/ | \ \
/ | \ \
| | \ |
__________|_____|____\_________|_________
M" O 1
Existem outros mapeamentos de olho de peixe que podem dar efeitos mais interessantes. Você decide.
Eu posso ver duas maneiras de implementar o efeito olho de peixe no HLSL.
Método 1: executar a projeção no vertex shader
Vantagem : quase nada precisa ser alterado no código. O shader de fragmento é extremamente simples. Ao invés de:
...
float4 screenPoint = mul(worldPoint, worldViewProjMatrix);
...
Você faz algo assim (provavelmente pode ser muito simplificado):
...
// This is optional, but it computes the z coordinate we will
// need later for Z sorting.
float4 out_Point = mul(in_Point, worldViewProjMatrix);
// This retrieves the world position so that we can project on
// the hemisphere. Normalise this vector and you get M'
float4 tmpPoint = mul(in_Point, worldViewMatrix);
// This computes the xy coordinates of M", which happen to
// be the same as M'.
out_Point.xy = tmpPoint.xy / length(tmpPoint.xyz);
...
Desvantagens : como todo o pipeline de renderização foi pensado para transformações lineares, a projeção resultante é exata para vértices, mas todas as variações estarão erradas, assim como as coordenadas de textura, e os triângulos ainda aparecerão como triângulos, mesmo que pareçam distorcidos.
Soluções alternativas : seria aceitável obter uma melhor aproximação enviando uma geometria refinada para a GPU, com mais subdivisões de triângulo. Isso também pode ser realizado em um sombreador de geometria, mas como essa etapa ocorre após o sombreador de vértice, o sombreador de geometria seria bastante complexo porque precisaria executar suas próprias projeções adicionais.
Método 2: executar a projeção no shader de fragmento
Outro método seria renderizar a cena usando uma projeção de grande angular e distorcer a imagem para obter um efeito olho de peixe usando um sombreador de fragmento de tela cheia.
Se o ponto M
tiver coordenadas (x,y)
na tela olho de peixe, significa que ele possui coordenadas (x,y,z)
na superfície do hemisfério, com z = sqrt(1-x*x-y*y)
. O que significa que havia coordenadas (ax,ay)
em nossa cena processadas com um FOV de theta
tal modo a = 1/(z*tan(theta/2))
. (Não tenho 100% de certeza da minha matemática aqui, vou verificar novamente hoje à noite).
O shader de fragmento seria, portanto, algo como isto:
void main(in float4 in_Point : POSITION,
uniform float u_Theta,
uniform sampler2D u_RenderBuffer,
out float4 out_FragColor : COLOR)
{
z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y);
float a = 1.0 / (z * tan(u_Theta * 0.5));
out_FragColor = tex2D(u_RenderBuffer, (in_Point.xy - 0.5) * 2.0 * a);
}
Vantagem : você obtém uma projeção perfeita, sem distorções, além daquelas devido à precisão do pixel.
Desvantagem : você não pode visualizar fisicamente a cena inteira, pois o FOV não pode atingir 180 graus. Além disso, quanto maior o FOV, pior a precisão no centro da imagem ... que é exatamente onde você deseja a máxima precisão.
Soluções alternativas : a perda de precisão pode ser aprimorada executando várias passagens de renderização, por exemplo 5, e fazendo a projeção da maneira de um mapa de cubo. Outra solução muito simples é simplesmente cortar a imagem final para o FOV desejado - mesmo que a lente tenha um FOV de 180 graus, você pode renderizar apenas uma parte dela. Isso é chamado de olho de peixe "full-frame" (que é meio irônico, pois dá a impressão de que você obtém algo "cheio" enquanto realmente corta a imagem).
(Nota: se você achou isso útil, mas não suficientemente claro, diga-me, tenho vontade de escrever um artigo mais detalhado sobre isso).