Equirectangular Mapping Shader

Hi, I have found this shader on this forum:

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 //Added by me
            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"
}

and it works fine. The only problem is that I want to put the texture inside the sphere. I started modifying it based on an other Shader I found (which is not working good enough but at least inverts the texture) but there is so much difference and I know almost nothing about shaders so I couldn’t do anything except changing the face culling.

An other problem with that shader is that it draw a brown line at the seem of the image. Can you please help me figuring out what the problem is?

Thanks.

You should reverse normals of sphere.
Add this script to your sphere.

Link:
http://wiki.unity3d.com/index.php/ReverseNormals

Or just invert the normals for the uv determination:

float2 equiUV = RadialCoords(-IN.normal);

The seam is probably at the top/bottom, because the texture is set to repeat/tile instead of clamp. In a perfect world, you want the U address to be tiling and the V address to be clamped. But fully clamped is better than fully tiling.

I need to adapt the shader to a Full screen custom pass in HDRP, has it been done before?

You’ll want to sample the texture with a mipmap bias in order to eliminate the seam; this will also net you a clearer image toward the poles of the image.

https://makc3d.wordpress.com/2017/01/19/sampling-equirectangular-textures/

If you want to render from the inside of the sphere, either create a 3d model of a sphere which is inverted, or set culling to either Off or Front.

I haven’t used HDRP, but adapting the concept to a full screen shader is definitely doable, but would take some significant shader work.

1 Like

thanks for your reply.
My project doesnt mapp the texture, i want to display the distorted texture (outpuiting to a projection system mapped onto a physical 'real ’ sphere). Here is my attempt of porting to HDRP so far, somehow seems mixed with the current display (see images below), what am I missing?

Shader "FullScreen/NewFullScreenCustomPass"
{

Properties{

        _Color("Color", Color) = (1,1,1,1)


    }


    HLSLINCLUDE

    #pragma vertex Vert
    //#pragma vertex vert
 
    #pragma target 4.5
    #pragma only_renderers d3d11 ps4 xboxone vulkan metal switch

    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
    #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"

    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/RenderPass/CustomPass/CustomPassCommon.hlsl"


    // The PositionInputs struct allow you to retrieve a lot of useful information for your fullScreenShader:
    // struct PositionInputs
     //{
    //     float3 positionWS;  // World space position (could be camera-relative)
    //    float2 positionNDC; // Normalized screen coordinates within the viewport    : [0, 1) (with the half-pixel offset)
    //     uint2  positionSS;  // Screen space pixel coordinates                       : [0, NumPixels)
    //     uint2  tileCoord;   // Screen tile coordinates                              : [0, NumTiles)
    //     float  deviceDepth; // Depth from the depth buffer                          : [0, 1] (typically reversed)
    //     float  linearDepth; // View space Z coordinate                              : [Near, Far]
    // };

    // To sample custom buffers, you have access to these functions:
    // But be careful, on most platforms you can't sample to the bound color buffer. It means that you
    // can't use the SampleCustomColor when the pass color buffer is set to custom (and same for camera the buffer).
    // float4 SampleCustomColor(float2 uv);
    // float4 LoadCustomColor(uint2 pixelCoords);
    // float LoadCustomDepth(uint2 pixelCoords);
    // float SampleCustomDepth(float2 uv);

    // There are also a lot of utility function you can use inside Common.hlsl and Color.hlsl,
    // you can check them out in the source code of the core SRP package.

    float4 _Color;
    float M_PI = 3.141592654f;
    // sampler2D _MainTex;
    // TEXTURE2D(_MainTex);
    SAMPLER(sampler_MainTex);
            float4 _MainTex_ST;

    float4 FullScreenPass(Varyings varyings) : SV_Target
   // fixed4 FullScreenPass(Varyings varyings) : SV_Target
    {
    
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);

        float depth = LoadCameraDepth(varyings.positionCS.xy);
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
      
               float2 uv =  posInput.positionNDC.xy * _RTHandleScale.xy;

                float theta = (uv.x*2 + 1) * 3.141592654;
                //float theta = (varyings.positionCS.x*2 + 1) * 3.141592654;
               float phi = (-(uv.y * 2 - 1) * 3.141592654) / 2;
               // float phi = (-(varyings.positionCS.y * 2 - 1) * 3.141592654) / 2;
                float x = cos(phi) * sin(theta);
                float y = sin(phi);
                float z = cos(phi) * cos(theta);
                float scale;
                float2 px;
                float2 offset;

       // float3 viewDirection = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
        float4 colortest =  float4(posInput.positionNDC.x, posInput.positionNDC.y, posInput.positionNDC.x, 0.9);
        float c = float4(0,0,0,1);
        // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
     //   if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
       //   float4  color = float4(CustomPassLoadCameraColor(varyings.positionCS.xy, 0), 1);


         // Pull from each of the 6 textures based on the phi/theta pair
                if (abs(x) >= abs(y) && abs(x) >= abs(z))
                {     
                    if (x < 0)
                    {
                        scale = -1.0 / x;
                        px.x = (z * scale + 1.0) / 2.0f;
                        px.y = (y * scale + 1.0) / 2.0f;
                        // Left
                      //  colortest = float4 ( 0.9, 0.0, 0.9 ,0.0);
                       // return float4 ( 0.8, 0.5, 0.9 ,1.0);
                       // return tex2D(_MainTex, float2(px.x / 4, 1 - ((px.y + 1) / 3 )));
                       //  float4 c = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, float2(px.x / 4, 1 - ((px.y + 1) / 3 )));
                        // colortest = c;
                        //  return float4(c.rgb, 0.5);
                         c = tex2Dlod (sampler_MainTex, float4(px.x / 4, 1 - ((px.y + 1) / 3 ), 0,1));
                       return c;
                        //return Texture2D(_MainTex, float2(px.x / 4, 1 - ((px.y + 1) / 3 )));
                    }

                 else
                    {
                        scale = 1.0 / x;
                        px.x = (-z * scale + 1.0) / 2.0f;
                        px.y = (y * scale + 1.0) / 2.0f;
                        offset.x = 2;
                        offset.y = 1;
                        // Right             
                        colortest =  float4 ( 0.5, 0.5, 0.5 ,0.5);
                        c =  tex2Dlod (sampler_MainTex, float4(px.x / 4 + .5f, 1 - ((px.y + 1) / 3), 0, 1));
                    return c;
                       // return tex2D(_MainTex, float2(px.x / 4 + .5f, 1 - ((px.y + 1) / 3)));
                    }
                }
                else if (abs(y) >= abs(z))
                {
                    if (y < 0)
                    {
                        scale = -1.0f / y;
                        px.x = (x * scale + 1.0) / 2.0f;
                        px.y = (z * scale + 1.0) / 2.0f;
                        // Top
                       colortest =  float4 ( 0.0, 0.9, 0.0 ,0.5);
                       c = tex2Dlod (sampler_MainTex, float4(px.x / 4 + .25f, 1 - (px.y / 3), 0,1));
                       return c;
                // return tex2D(_MainTex, float2(px.x / 4 + .25f, 1 - (px.y / 3)));
                    }
                    else
                    {
                        scale = 1.0 / y;
                        px.x = (x * scale + 1.0) / 2.0f;
                        px.y =1 - (-z * scale + 1.0) / 2.0f;
            
                        // Bottom
                                            colortest =  float4 ( 0.9, 0.2, 0.2 ,0.5);
                    c = tex2Dlod (sampler_MainTex, float4(px.x / 4 + .25f, (px.y / 3), 0,1));
                    return c;
                     //  return tex2D(_MainTex, float2(px.x / 4 + .25f, px.y / 3));
            
                    }
                }
                else
                {
                    if (z < 0)
                    {
                        scale = -1.0 / z;
                        px.x = (-x * scale + 1.0) / 2.0f;
                        px.y = (y * scale + 1.0) / 2.0f;
                        // Back
                         colortest = float4 ( 0.5, 0.8, 0.5 ,0.5);
                            c = tex2Dlod (sampler_MainTex, float4(px.x / 4 + .75f, 1 - ((px.y + 1) / 3 ), 0, 1));

                             c=  float4
                        return c;
                        //return tex2D(_MainTex, float2(px.x / 4 + .75f, 1 - ((px.y + 1) / 3 )));
                    }
                    else
                    {
                        scale = 1.0 / z;
                        px.x = (x * scale + 1.0) / 2.0f;
                        px.y = (y * scale + 1.0) / 2.0f;
                        // Front
                        colortest =  float4 ( 0.2, 0.0, 0.5 ,0.5);
                        c = tex2Dlod (sampler_MainTex, float4(px.x / 4 + .25f, 1 - ((px.y + 1) / 3), 0, 1));
                        return c;
                       // return tex2D(_MainTex, float2(px.x / 4 + .25f, 1 - ((px.y + 1) / 3)));
                    }
                }
                // Should never come here
        // Fade value allow you to increase the strength of the effect while the camera gets closer to the custom pass volume
  
      return float4 ( colortest.rgb ,_Color.a);
 
    }

    ENDHLSL


    SubShader
    {
 
        Pass
        {
            Name "Custom Pass 0"

            ZWrite Off
            ZTest LEqual//Always
            Blend SrcAlpha OneMinusSrcAlpha
            Cull Off //Front

            HLSLPROGRAM
                #pragma fragment FullScreenPass
    
            ENDHLSL

  

  
        }
    }
    Fallback Off
}



Got few questions about the projection, appreciate if you can help me understand:

  1. the TEXCOORD0 coord got a Z component, is it always 1 (or a const across all texture pixels)?
  2. How the coords returned by RadialCoords are in the 2D space, how does it map to a 3D point on the sphere?

I had the same problem and the solution to remove the brown line is to set minFilter and magFilter to LinearFilter in your texture definition.