【发布时间】:2016-03-18 23:19:52
【问题描述】:
我想从立方体贴图 [图 1] 转换为等角全景图 [图 2]。
可以从 Spherical 转到 Cubic(通过以下方式:Convert 2:1 equirectangular panorama to cube map),但不知道如何反转它。
Figure2 将使用 Unity 渲染成一个球体。
【问题讨论】:
标签: c++ opencv image-processing unity3d
我想从立方体贴图 [图 1] 转换为等角全景图 [图 2]。
可以从 Spherical 转到 Cubic(通过以下方式:Convert 2:1 equirectangular panorama to cube map),但不知道如何反转它。
Figure2 将使用 Unity 渲染成一个球体。
【问题讨论】:
标签: c++ opencv image-processing unity3d
假设输入图像为以下立方体贴图格式:
我们的目标是将图像投影为 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 中为您的输入图像设置正确的导入设置:
【讨论】:
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));(我添加了自己的最大函数)。
cube2sphere 使整个过程自动化。示例:
$ cube2sphere front.jpg back.jpg right.jpg left.jpg top.jpg bottom.jpg -r 2048 1024 -fTGA -ostitched
【讨论】: