【问题标题】:Converting a Cubemap into Equirectangular Panorama将 Cubemap 转换为 Equirectangular Panorama
【发布时间】:2016-03-18 23:19:52
【问题描述】:

我想从立方体贴图 [图 1] 转换为等角全景图 [图 2]。

图1

图2

可以从 Spherical 转到 Cubic(通过以下方式:Convert 2:1 equirectangular panorama to cube map),但不知道如何反转它。

Figure2 将使用 Unity 渲染成一个球体。

【问题讨论】:

    标签: c++ opencv image-processing unity3d


    【解决方案1】:

    假设输入图像为以下立方体贴图格式:

    我们的目标是将图像投影为 equirectangular 格式,如下所示:

    转换算法相当简单。 为了在给定具有 6 个面的立方体贴图的情况下计算 equirectangular 图像中每个像素的颜色的最佳估计

    • 首先,计算每个像素对应的极坐标 球面图像。
    • 其次,使用极坐标形成一个向量并确定 立方体贴图的哪个面和那个面的哪个像素是向量 谎言;就像来自立方体中心的光线投射会击中其中一个 它的侧面和那一侧的特定点。

    请记住,在给定立方体贴图特定面上的归一化坐标 (u,v) 的情况下,有多种方法可以估计 equirectangular 图像中像素的颜色。最基本的方法是非常原始的近似值,为简单起见将在此答案中使用,是将坐标四舍五入到特定像素并使用该像素。其他更高级的方法可以计算几个相邻像素的平均值。

    算法的实现会因上下文而异。我在 Unity3D C# 中做了一个快速实现,展示了如何在现实世界场景中实现算法。它运行在CPU上,有很大的改进空间,但很容易理解。

    using UnityEngine;
    
    public static class CubemapConverter
    {
        public static byte[] ConvertToEquirectangular(Texture2D sourceTexture, int outputWidth, int outputHeight)
        {
            Texture2D equiTexture = new Texture2D(outputWidth, outputHeight, TextureFormat.ARGB32, false);
            float u, v; //Normalised texture coordinates, from 0 to 1, starting at lower left corner
            float phi, theta; //Polar coordinates
            int cubeFaceWidth, cubeFaceHeight;
    
            cubeFaceWidth = sourceTexture.width / 4; //4 horizontal faces
            cubeFaceHeight = sourceTexture.height / 3; //3 vertical faces
    
    
            for (int j = 0; j < equiTexture.height; j++)
            {
                //Rows start from the bottom
                v = 1 - ((float)j / equiTexture.height);
                theta = v * Mathf.PI;
    
                for (int i = 0; i < equiTexture.width; i++)
                {
                    //Columns start from the left
                    u = ((float)i / equiTexture.width);
                    phi = u * 2 * Mathf.PI;
    
                    float x, y, z; //Unit vector
                    x = Mathf.Sin(phi) * Mathf.Sin(theta) * -1;
                    y = Mathf.Cos(theta);
                    z = Mathf.Cos(phi) * Mathf.Sin(theta) * -1;
    
                    float xa, ya, za;
                    float a;
    
                    a = Mathf.Max(new float[3] { Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z) });
    
                    //Vector Parallel to the unit vector that lies on one of the cube faces
                    xa = x / a;
                    ya = y / a;
                    za = z / a;
    
                    Color color;
                    int xPixel, yPixel;
                    int xOffset, yOffset;
    
                    if (xa == 1)
                    {
                        //Right
                        xPixel = (int)((((za + 1f) / 2f) - 1f) * cubeFaceWidth);
                        xOffset = 2 * cubeFaceWidth; //Offset
                        yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                        yOffset = cubeFaceHeight; //Offset
                    }
                    else if (xa == -1)
                    {
                        //Left
                        xPixel = (int)((((za + 1f) / 2f)) * cubeFaceWidth);
                        xOffset = 0;
                        yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                        yOffset = cubeFaceHeight;
                    }
                    else if (ya == 1)
                    {
                        //Up
                        xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                        xOffset = cubeFaceWidth;
                        yPixel = (int)((((za + 1f) / 2f) - 1f) * cubeFaceHeight);
                        yOffset = 2 * cubeFaceHeight;
                    }
                    else if (ya == -1)
                    {
                        //Down
                        xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                        xOffset = cubeFaceWidth;
                        yPixel = (int)((((za + 1f) / 2f)) * cubeFaceHeight);
                        yOffset = 0;
                    }
                    else if (za == 1)
                    {
                        //Front
                        xPixel = (int)((((xa + 1f) / 2f)) * cubeFaceWidth);
                        xOffset = cubeFaceWidth;
                        yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                        yOffset = cubeFaceHeight;
                    }
                    else if (za == -1)
                    {
                        //Back
                        xPixel = (int)((((xa + 1f) / 2f) - 1f) * cubeFaceWidth);
                        xOffset = 3 * cubeFaceWidth;
                        yPixel = (int)((((ya + 1f) / 2f)) * cubeFaceHeight);
                        yOffset = cubeFaceHeight;
                    }
                    else
                    {
                        Debug.LogWarning("Unknown face, something went wrong");
                        xPixel = 0;
                        yPixel = 0;
                        xOffset = 0;
                        yOffset = 0;
                    }
    
                    xPixel = Mathf.Abs(xPixel);
                    yPixel = Mathf.Abs(yPixel);
    
                    xPixel += xOffset;
                    yPixel += yOffset;
    
                    color = sourceTexture.GetPixel(xPixel, yPixel);
                    equiTexture.SetPixel(i, j, color);
                }
            }
    
            equiTexture.Apply();
            var bytes = equiTexture.EncodeToPNG();
            Object.DestroyImmediate(equiTexture);
    
            return bytes;
        }
    }
    

    为了利用 GPU,我创建了一个进行相同转换的着色器。它比在 CPU 上逐个像素地运行转换要快得多,但不幸的是 Unity 对立方体贴图施加了分辨率限制,因此在使用高分辨率输入图像的情况下它的用处受到限制。

    Shader "Conversion/CubemapToEquirectangular" {
      Properties {
            _MainTex ("Cubemap (RGB)", CUBE) = "" {}
        }
    
        Subshader {
            Pass {
                ZTest Always Cull Off ZWrite Off
                Fog { Mode off }      
    
                CGPROGRAM
                    #pragma vertex vert
                    #pragma fragment frag
                    #pragma fragmentoption ARB_precision_hint_fastest
                    //#pragma fragmentoption ARB_precision_hint_nicest
                    #include "UnityCG.cginc"
    
                    #define PI    3.141592653589793
                    #define TWOPI 6.283185307179587
    
                    struct v2f {
                        float4 pos : POSITION;
                        float2 uv : TEXCOORD0;
                    };
    
                    samplerCUBE _MainTex;
    
                    v2f vert( appdata_img v )
                    {
                        v2f o;
                        o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                        o.uv = v.texcoord.xy * float2(TWOPI, PI);
                        return o;
                    }
    
                    fixed4 frag(v2f i) : COLOR 
                    {
                        float theta = i.uv.y;
                        float phi = i.uv.x;
                        float3 unit = float3(0,0,0);
    
                        unit.x = sin(phi) * sin(theta) * -1;
                        unit.y = cos(theta) * -1;
                        unit.z = cos(phi) * sin(theta) * -1;
    
                        return texCUBE(_MainTex, unit);
                    }
                ENDCG
            }
        }
        Fallback Off
    }
    

    通过在转换过程中采用更复杂的方法来估计像素的颜色或通过对生成的图像进行后处理(或两者兼而有之,实际上),可以大大提高生成的图像的质量。例如,可以生成更大尺寸的图像以应用模糊滤镜,然后将其下采样到所需尺寸。

    我创建了一个简单的 Unity 项目,其中包含两个编辑器向导,展示了如何正确利用 C# 代码或上面显示的着色器。在这里获取: https://github.com/Mapiarz/CubemapToEquirectangular

    记得在 Unity 中为您的输入图像设置正确的导入设置:

    • 点过滤
    • 真彩色格式
    • 禁用 mipmaps
    • 2 的非幂:无(仅适用于 2D 纹理)
    • 启用读/写(仅适用于 2D 纹理)

    【讨论】:

    • 如果我想在将立方体贴图映射到球体之前旋转立方体贴图(然后将其映射到 2D 面上),我该怎么做?
    • @Bartosz - 我已将此代码转换为 c++ openCV,我在这一行遇到问题 a = Mathf.Max(new float[3] { Mathf.Abs(x), Mathf.Abs(y), Mathf.Abs(z) }); - 因为当 x、y、z 值低于 1 时,a 返回为 0,由于除以零的问题,这使得 xa、xy 和 xz 未定义,我错过了什么吗?我的 c++ 行如下所示:a = maximum(abs(x), abs(y), abs(z));(我添加了自己的最大函数)。
    • 嗨!我已经分析了一段时间的代码。您对超过 6 张面孔的解决方案有任何想法吗?在您的情况下,您有 6 个面,每个面对应 1 个轴(x、y、z、-x、-y、-z)如果有多个面/图像,每个图像都有自己的“面”,它也是自己的“轴”。这是我的想法,我无法从这里继续。你能帮我吗?
    • 这是一个很好的答案
    • 谢谢!如果想使用 6 个单独的纹理生成球面图,每个纹理代表一个边怎么办?
    【解决方案2】:

    cube2sphere 使整个过程自动化。示例:

    $ cube2sphere front.jpg back.jpg right.jpg left.jpg top.jpg bottom.jpg -r 2048 1024 -fTGA -ostitched
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-21
      • 1970-01-01
      • 2016-05-05
      • 1970-01-01
      • 2019-01-22
      • 2015-06-23
      • 1970-01-01
      • 2020-05-27
      相关资源
      最近更新 更多