360 viewer in unity, texture appears warped in the top and bottom

6.8k Views Asked by At

I am making a 360 viewer in unity, to view a 360 photo I used to have a cubemap attached to a skybox, and it worked great. But the weight of the cubemaps forced me to switch to textures.

All of the 360 viewer tutorials say to just put a sphere with a shader on it, and put the camera inside. When I do this, it doesn't work very well, because when I look to the top or bottom, I see the image warped like so: (The chairs are suppossed to look normal) enter image description here

It did not happen when I used a skybox.

Does any one know why is this happening?

Thank you very much!

4

There are 4 best solutions below

7
On BEST ANSWER

The shader you choose does not handle equirectangular distortion very well. At the poles (top and bottom) of the sphere much image information has to be mapped on very small space which leads to the artifacts you are seeing.

You can write a specialized shader to improve the coordinate mapping from your equirectangular image onto the sphere. At the Unity forums a specialized shader has been posted.

Shader "Custom/Equirectangular" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "gray" {}
    }

    SubShader{
        Pass {
            Tags {"LightMode" = "Always"}

            CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                #pragma glsl
                #pragma target 3.0

                #include "UnityCG.cginc"

                struct appdata {
                   float4 vertex : POSITION;
                   float3 normal : NORMAL;
                };

                struct v2f
                {
                    float4    pos : SV_POSITION;
                    float3    normal : TEXCOORD0;
                };

                v2f vert (appdata v)
                {
                    v2f o;
                    o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                    o.normal = v.normal;
                    return o;
                }

                sampler2D _MainTex;

                #define PI 3.141592653589793

                inline float2 RadialCoords(float3 a_coords)
                {
                    float3 a_coords_n = normalize(a_coords);
                    float lon = atan2(a_coords_n.z, a_coords_n.x);
                    float lat = acos(a_coords_n.y);
                    float2 sphereCoords = float2(lon, lat) * (1.0 / PI);
                    return float2(sphereCoords.x * 0.5 + 0.5, 1 - sphereCoords.y);
                }

                float4 frag(v2f IN) : COLOR
                {
                    float2 equiUV = RadialCoords(IN.normal);
                    return tex2D(_MainTex, equiUV);
                }
            ENDCG
        }
    }
    FallBack "VertexLit"
}

Again, it's not my own code but I tested it on android devices and as a standalone PC version. It results in very smooth poles.

Please note: This shader does not flip normals of your sphere. So if you want your camera to sit inside the sphere you have to invert its normals with a 3d program or with the shader. Try adding Cull Front after line 9 above and the shader will apply its texture to the "wrong" side of the model.

0
On

I'm a beginner and I had to do a lot just to understand this thread. This is what worked for me. I just combined the answers and put it in one script. I'm pretty sure I will forget this in a few weeks time, so putting it here for posterity.

Shader "Custom/Equirectangular" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "gray" {}
    }

    SubShader{
        Pass {
            Tags {"LightMode" = "Always"}
            Cull Front

            CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                #pragma glsl
                #pragma target 3.0

                #include "UnityCG.cginc"

                struct appdata {
                   float4 vertex : POSITION;
                   float3 normal : NORMAL;
                };

                struct v2f
                {
                    float4    pos : SV_POSITION;
                    float3    normal : TEXCOORD0;
                };

                v2f vert (appdata v)
                {
                    v2f o;
                    o.pos = UnityObjectToClipPos(v.vertex);
                    o.normal = v.normal;
                    return o;
                }

                sampler2D _MainTex;

                #define PI 3.141592653589793

                inline float2 RadialCoords(float3 a_coords)
                {
                    float3 a_coords_n = normalize(a_coords);
                    float lon = atan2(a_coords_n.z, a_coords_n.x);
                    float lat = acos(a_coords_n.y);
                    float2 sphereCoords = float2(lon, lat) * (1.0 / PI);
                    return float2(1 - (sphereCoords.x * 0.5 + 0.5), 1 - sphereCoords.y);
                }

                float4 frag(v2f IN) : COLOR
                {
                    float2 equiUV = RadialCoords(IN.normal);
                    return tex2D(_MainTex, equiUV);
                }
            ENDCG
        }
    }
    FallBack "VertexLit"
}
0
On

This shader is working for flipping normals as well although it flips the image It's just a matter of putting a minus sign in front of the value of the tangent. Got the code from a Unity User.

Shader "Unlit/PanoramaShader"{
Properties
{
    _MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
    Tags { "RenderType" = "Opaque" }
    LOD 100

    // Render the object inside-out.
    Cull Front

    Pass
    {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_fog

        #include "UnityCG.cginc"

        struct appdata
        {
            float4 vertex : POSITION;
        };

        struct v2f
        {               
            float4 vertex : SV_POSITION;
            // Pass a view direction instead of a UV coordinate.
            float3 direction : TEXCOORD0;
        };

        sampler2D _MainTex;
        float4 _MainTex_ST;

        v2f vert (appdata v)
        {
            v2f o;
            o.vertex = UnityObjectToClipPos(v.vertex);
            // Compute worldspace direction from the camera to this vertex.
            o.direction = mul(unity_ObjectToWorld, v.vertex).xyz 
                           - _WorldSpaceCameraPos;
            return o;
        }

        fixed4 frag (v2f i) : SV_Target
        {
            // Convert the direction to the fragment into latitude & longitude.
            float3 pos = normalize(i.direction);
            float2 uv;       
            uv.x = -atan2(pos.z, pos.x)* 0.5f;
            uv.y = asin(pos.y);

            // Scale and shift into the 0...1 texture coordinate range.
            uv = uv / 3.141592653589f + 0.5f;

            // Used directly, we'll get a texture filtering seam
            // where the longitude wraps around from 1 to 0.
            // This fixes that (you can skip this if your videos don't mipmap)
            float2 dx = ddx(uv);
            float2 dy = ddy(uv);
            float2 du = float2(dx.x, dy.x);
            du -= (abs(du) > 0.5f) * sign(du);
            dx.x = du.x;
            dy.x = du.y;

            // In case you want to rotate your view using the texture x-offset.
            uv.x += _MainTex_ST.z;     

            // Sample the texture with our calculated UV & seam fixup.
            fixed4 col = tex2Dgrad(_MainTex, uv, dx, dy);

            return col;
        }
        ENDCG
    }
}}
0
On

This is another shader code.

'Shader "Flip Normals" {
          Properties {
             _MainTex ("Base (RGB)", 2D) = "white" {}
         }
          SubShader {

            Tags { "RenderType" = "Opaque" }

            Cull Front

            CGPROGRAM

            #pragma surface surf Lambert vertex:vert
            sampler2D _MainTex;

             struct Input {
                 float2 uv_MainTex;
                 float4 color : COLOR;
             };


            void vert(inout appdata_full v)
            {
                v.normal.xyz = v.normal * -1;
            }

            void surf (Input IN, inout SurfaceOutput o) {
                      fixed3 result = tex2D(_MainTex, IN.uv_MainTex);
                 o.Albedo = result.rgb;
                 o.Alpha = 1;
            }

            ENDCG

          }

          Fallback "Diffuse"
     }`