如何使用HLSL创建广角/鱼眼镜头?


29

为了获得四肢广角镜的效果,需要实施哪些概念?

伪代码和涉及内容流水线各个阶段的具体说明,以及需要从源代码传递到HLSL的哪些信息,将非常有用。

另外,安装广角镜和鱼眼镜头有什么区别?

Answers:


37

广角镜头的行为不应与其他普通镜头型号有所不同。它们只是具有较大的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中实现鱼眼效果的方法。

方法1:在顶点着色器上执行投影

优点:代码几乎不需要更改。片段着色器非常简单。而不是:

...
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,以获得更好的近似值是可以接受的。这也可以在几何着色器中执行,但是由于此步骤发生在顶点着色器之后,因此几何着色器将非常复杂,因为它必须执行自己的其他投影。

方法2:在片段着色器上执行投影

另一种方法是使用广角投影渲染场景,然后使用全屏片段着色器对图像进行扭曲以实现鱼眼效果。

如果点在鱼眼屏幕中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,您也可能只希望渲染其中一部分。这就是所谓的“全画幅”鱼眼(有点讽刺意味,因为它给人的印象是您在实际裁剪图像时会得到“全幅”的东西)。

(注意:如果您发现此功能有用但不够清楚,请告诉我,我想写一篇更详细的文章)。


非常有用,欢迎您全心全意写出更详细的文章!
SirYakalot 2011年

可以将两种方法结合起来以获得更好的结果吗?首先在VS中进行投影以使所有内容都可见,然后在PS中取消投影并再次进行投影以获取正确的UV和所有内容吗?可能需要向PS发送更多的参数,才能正确地取消投影到原始图像。
Ondrej Petrzilka,2015年

3

应该是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);
}

嘿@Josh,fovTheta是如何计算的?
tom 2012年

1
我只编辑了此答案以调整格式,我相信您想直接解决@bman。
2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.