Answers:
广角镜头的行为不应与其他普通镜头型号有所不同。它们只是具有较大的FOV(在D3DXMatrixPerspectiveFovLH
某种意义上-我假设您使用的是DirectX),或具有较大的left / right和bottom / top值(就OpenGL而言glFrustum
)。
我相信真正有趣的部分在于对鱼眼镜头建模。有鱼眼地震,你可以学习,它带有源。
但是,鱼眼镜头的投影是高度非线性的。在更常见的镜头(据我所知,仅限于监视摄像机)中,M
空间点投射到单位半球的表面上,然后该表面平行投射到单位圆盘上:
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
还有其他鱼眼贴图可能会提供更有趣的效果。由你决定。
我可以看到两种在HLSL中实现鱼眼效果的方法。
优点:代码几乎不需要更改。片段着色器非常简单。而不是:
...
float4 screenPoint = mul(worldPoint, worldViewProjMatrix);
...
您可以执行以下操作(可能会简化很多):
...
// 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);
...
缺点:由于整个渲染管线都被认为是线性变换,因此所得到的投影对于顶点是精确的,但是所有变化以及纹理坐标都是错误的,即使三角形看起来会变形,三角形仍将显示为三角形。
解决方法:通过向三角形发送细分的几何图形到GPU,以获得更好的近似值是可以接受的。这也可以在几何着色器中执行,但是由于此步骤发生在顶点着色器之后,因此几何着色器将非常复杂,因为它必须执行自己的其他投影。
另一种方法是使用广角投影渲染场景,然后使用全屏片段着色器对图像进行扭曲以实现鱼眼效果。
如果点在鱼眼屏幕中M
具有坐标(x,y)
,则表示该(x,y,z)
点在半球表面具有坐标,用z = sqrt(1-x*x-y*y)
。这意味着它(ax,ay)
在我们的场景中具有以theta
这样的FOV渲染的坐标a = 1/(z*tan(theta/2))
。(不是100%肯定我在这里的数学,今晚我将再次检查)。
因此,片段着色器将是这样的:
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);
}
优点:您可以获得完美的投影,并且不会因像素精度而导致失真。
缺点:由于FOV不能达到180度,因此您无法实际查看整个场景。同样,FOV越大,图像中心的精度越差……这正是您要获得最大精度的地方。
解决方法:可以通过执行几次渲染过程(例如5次)并以立方体贴图的方式进行投影来改善精度损失。另一个非常简单的解决方法是仅将最终图像裁剪为所需的FOV-即使镜头本身具有180度FOV,您也可能只希望渲染其中一部分。这就是所谓的“全画幅”鱼眼(有点讽刺意味,因为它给人的印象是您在实际裁剪图像时会得到“全幅”的东西)。
(注意:如果您发现此功能有用但不够清楚,请告诉我,我想写一篇更详细的文章)。
应该是z = sqrt(1.0 - in_Point.x * in_Point.x - in_Point.y * in_Point.y)
吧?
我的GLSL实现是:
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float fovTheta; // FOV's theta
// fisheye
void main (void)
{
vec2 uv = v_texCoord - 0.5;
float z = sqrt(1.0 - uv.x * uv.x - uv.y * uv.y);
float a = 1.0 / (z * tan(fovTheta * 0.5));
// float a = (z * tan(fovTheta * 0.5)) / 1.0; // reverse lens
gl_FragColor = texture2D(u_texture, (uv* a) + 0.5);
}