Un objectif grand angle ne doit pas se comporter différemment des autres modèles d'objectifs standard. Ils ont juste un FOV plus grand (dans le D3DXMatrixPerspectiveFovLH
sens - je suppose que vous utilisez DirectX), ou des valeurs plus grandes gauche / droite et bas / haut (dans l'OpenGLglFrustum
sens ).
Je crois que la partie vraiment intéressante réside dans la modélisation de l'objectif fisheye. Il y a Fisheye Quake que vous pouvez étudier, il vient avec la source.
La vraie projection fisheye
La projection d'un objectif fisheye est cependant très non linéaire. Dans le type d'objectif le plus courant (à ma connaissance, limité aux caméras de surveillance), un point M
dans l'espace est projeté sur la surface d'un hémisphère unitaire, puis cette surface subit une projection parallèle sur le disque unitaire:
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
Il existe d'autres mappages fisheye qui peuvent donner des effets plus intéressants. C'est à vous.
Je peux voir deux façons de mettre en œuvre l'effet fisheye dans HLSL.
Méthode 1: effectuer la projection sur le vertex shader
Avantage : presque rien ne doit être changé dans le code. Le shader de fragment est extrêmement simple. Plutôt que:
...
float4 screenPoint = mul(worldPoint, worldViewProjMatrix);
...
Vous faites quelque chose comme ça (peut probablement être simplifié beaucoup):
...
// 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);
...
Inconvénients : comme tout le pipeline de rendu a été pensé pour les transformations linéaires, la projection résultante est exacte pour les sommets, mais toutes les variations seront fausses, ainsi que les coordonnées de texture, et les triangles apparaîtront toujours comme des triangles même s'ils doivent apparaître déformés.
Solutions de contournement : il pourrait être acceptable d'obtenir une meilleure approximation en envoyant une géométrie raffinée au GPU, avec plus de subdivisions de triangle. Cela peut également être effectué dans un shader de géométrie, mais comme cette étape se produit après le vertex shader, le geometry shader serait assez complexe car il devrait effectuer ses propres projections supplémentaires.
Méthode 2: effectuez la projection sur le fragment shader
Une autre méthode consisterait à rendre la scène à l'aide d'une projection grand angle, puis à déformer l'image pour obtenir un effet fisheye à l'aide d'un ombrage de fragments en plein écran.
Si le point M
a des coordonnées (x,y)
dans l'écran fisheye, cela signifie qu'il avait des coordonnées (x,y,z)
sur la surface de l'hémisphère, avec z = sqrt(1-x*x-y*y)
. Ce qui signifie qu'il y avait des coordonnées (ax,ay)
dans notre scène rendues avec un FOV de ce theta
type a = 1/(z*tan(theta/2))
. (Je ne suis pas sûr à 100% de mes calculs ici, je vérifierai à nouveau ce soir).
Le fragment shader serait donc quelque chose comme ceci:
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);
}
Avantage : vous obtenez une projection parfaite sans distorsions en dehors de celles dues à la précision des pixels.
Inconvénient : vous ne pouvez pas visualiser physiquement toute la scène, car le FOV ne peut pas atteindre 180 degrés. De plus, plus le champ de vision est grand, plus la précision au centre de l'image est mauvaise ... c'est précisément là que vous souhaitez une précision maximale.
Solutions de contournement : la perte de précision peut être améliorée en effectuant plusieurs passes de rendu, par exemple 5, et en effectuant la projection à la manière d'une carte de cube. Une autre solution de contournement très simple consiste à recadrer simplement l'image finale dans le champ de vision souhaité - même si l'objectif lui-même a un champ de vision à 180 degrés, vous souhaiterez peut-être n'en rendre qu'une partie. C'est ce qu'on appelle le fisheye "plein cadre" (ce qui est un peu ironique, car cela donne l'impression que vous obtenez quelque chose de "complet" alors qu'il recadre l'image).
(Remarque: si vous avez trouvé cela utile mais pas assez clair, dites-le moi, j'ai envie d'écrire un article plus détaillé à ce sujet).
Ça devrait l'être
z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y)
, non?Mon implémentation GLSL est:
la source