Unity has a built in Skybox shader that takes either a cubemap texture or an equirectangular texture like this
Loading it in and following the instructions to use it as a skybox works
I want to extend it to handle a fisheye image like this
The code for the shader is available from the built in shaders and a beta version (which at a glance seems the same) is available here
Looking through the shader code a 3D direction is computed in the vertex shader and the passed to the fragment shader. The fragment shader is then supposed to take that 3D direction and generate a texture coordinate.
Here's the code for equirectangular images
inline float2 ToRadialCoords(float3 coords)
{
float3 normalizedCoords = normalize(coords);
float latitude = acos(normalizedCoords.y);
float longitude = atan2(normalizedCoords.z, normalizedCoords.x);
float2 sphereCoords = float2(longitude, latitude) * float2(0.5/UNITY_PI, 1.0/UNITY_PI);
return float2(0.5,1.0) - sphereCoords;
}
Here's the code I tried to change it to for the fisheye image
inline float2 ToFisheyeCoords(float3 coords)
float3 normalizedCoords = normalize(coords);
float r = 2.0 * atan2(length(normalizedCoords.xy), abs(normalizedCoords.z)) / UNITY_PI;
float theta = atan2(normalizedCoords.y, normalizedCoords.x * sign(normalizedCoords.z));
float2 uv = float2(cos(theta), sin(theta)) * r * 0.5 + 0.5;
return frac(uv * float2(-1, 1));
}
But it's not working.
I feel like I'm overlooking something obvious.
The entire project is here. To switch between the fisheye example and the equirectangular example you need to open Window->Rendering->Light Settings and then drag the SkyboxMaterialEquirectangular into the Skybox Material slot in the Lighting window.
I played around with this a bit and I figured I'd post it. The only thing to add to your answer is, in the 360 case, choosing which half of the image to sample.
inline float2 ToFisheyeCoords(float3 coords)
{
float3 n = normalize(coords);
// u = r cos(phi) + 0.5
// v = r sin(phi) + 0.5
//where
// r = atan2(sqrt(x * x + y * y), p.z) / pi
// phi = atan2(y, x)
float r = atan2(length(n.xy), abs(n.z)) / UNITY_PI;
float phi = atan2(n.y, n.x * sign(n.z));
float2 uv = float2(cos(phi), sin(phi)) * r + .5;
uv.x *= .5;
//Choose image half to sample depending on sign of normal.z
uv.x += .25*(1 - sign(n.z));
return uv;
}
A more general solution for a single (not split) image the seems to be:
inline float2 ToFisheyeCoords(float3 coords)
{
float3 n = normalize(coords);
//float FOV = UNITY_PI; // 180 degrees
float FOV = UNITY_PI*2; // 360 degrees
float r = atan2(length(n.xy), n.z) / FOV;
float phi = atan2(n.y, n.x);
float2 uv = float2(cos(phi), sin(phi)) * r + .5;
return saturate(uv);
}