Projection of skymap to objects in VR (Single Pass Stereo)

Hello,

for my open source RC helicopter game, where the pilot is standing at the constant position in the middle of a 360° skybox photo-image, and only the camera orientation changes, I used a custom shader to project the skybox photo-images onto the “ground object” (ground, trees, houses,…). Therefore a second camera is rendered into a “render texture” which is used in the shader: If the helicopter flies behind trees for examples, then it gets covered. The “ground object” catches also shadows.

Using the old non VR shader and deactivating XR leads to correct results:

I would like to change my existing shader to work in VR with the Single Pass Stereo rendering method. But due to lack of knowledge and experience I don’t get it work.

I followed the explanation in the documentation “Single Pass Instanced rendering” → Custom shaders (Unity - Manual: Single-pass instanced rendering and custom shaders)

Here I’m not sure, if my “GPU instancing” settings are right, I don’t want to have different parameters for the mesh instances. Therefore I only added “#pragma multi_compile_instancing” and activated it in the material settings.

Continuing in the doku I added the macros at the suggested positions:

UNITY_VERTEX_INPUT_INSTANCE_ID, UNITY_VERTEX_OUTPUT_STEREO, UNITY_SETUP_INSTANCE_ID(), UNITY_INITIALIZE_OUTPUT(v2f, o) UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO().

But the projection isn’t working (see image below) and also only the left eye shadow is at the correct position.

Could please give me somebody an advice, what I have to do to fix the shader?

Shader for non VR mode (working fine) :

// source:
// https://www.ronja-tutorials.com/2019/01/20/screenspace-texture.html
// https://discussions.unity.com/t/624555 ?_ga=2.69737753.803013357.1578766070-2133810121.1564613509#post-2606783

Shader "Custom/ground"
{
    //show values to edit in inspector
    Properties{
        _Color("Tint", Color) = (0, 0, 0, 1)
        _MainTex("Texture", 2D) = "white" {}
        _ShadowStrength("Shadow Strength", Range(0, 1)) = 1
    }

    SubShader
    {

        //the material is completely non-transparent and is rendered at the same time as the other opaque geometry
        Tags{ "RenderType" = "Opaque" "Queue" = "Geometry" }

        Pass
        {
            CGPROGRAM

            //include useful shader functions
            #include "UnityCG.cginc"

            //define vertex and fragment shader
            #pragma vertex vert
            #pragma fragment frag

            //texture and transforms of the texture
            sampler2D _MainTex;
            float4 _MainTex_ST;

            //tint of the texture
            fixed4 _Color;

            //the object data that's put into the vertex shader
            struct appdata
            {
                float4 vertex : POSITION;
            };

            //the data that's used to generate fragments and can be read by the fragment shader
            struct v2f
            {
                float4 position : SV_POSITION;
                float4 screenPosition : TEXCOORD0;
            };

            //the vertex shader
            v2f vert(appdata v)
            {
                v2f o;
                //convert the vertex positions from object space to clip space so they can be rendered
                o.position = UnityObjectToClipPos(v.vertex);
                o.screenPosition = ComputeScreenPos(o.position);
                return o;
            }

            //the fragment shader
            fixed4 frag(v2f i) : SV_TARGET
            {
                fixed4 col = tex2Dproj(_MainTex, i.screenPosition);
                col *= _Color;
                return col;
            }

            ENDCG
        }



        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            Tags{ "LightMode" = "ForwardBase" }
            ZWrite Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                SHADOW_COORDS(0)
            };

            fixed _ShadowStrength;
            v2f vert(appdata_img v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag(v2f i) : COLOR
            {
                fixed shadow = SHADOW_ATTENUATION(i);
                fixed shadowalpha = (1.0 - shadow) * _ShadowStrength;
                return fixed4(0.0, 0.0, 0.0, shadowalpha);
            }

            ENDCG
        }



        Pass
        {
            Tags{ "LightMode" = "ShadowCaster" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster

            #include "UnityCG.cginc"

            struct v2f
            {
                V2F_SHADOW_CASTER;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                    return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }


    }
    //FallBack "Standard"
}

Shader edited for VR:

// source:
// https://www.ronja-tutorials.com/2019/01/20/screenspace-texture.html
// https://discussions.unity.com/t/624555 ?_ga=2.69737753.803013357.1578766070-2133810121.1564613509#post-2606783

Shader "Custom/ground2"
{
    //show values to edit in inspector
    Properties{
        _Color("Tint", Color) = (0, 0, 0, 1)
        _MainTex("Texture", 2D) = "white" {}
        _ShadowStrength("Shadow Strength", Range(0, 1)) = 1
    }

    SubShader
    {

        //the material is completely non-transparent and is rendered at the same time as the other opaque geometry
        Tags{ "RenderType" = "Opaque" "Queue" = "Geometry" }

        Pass
        {
            CGPROGRAM

            //include useful shader functions
            #include "UnityCG.cginc"

            //define vertex and fragment shader
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing  // ##### ADDED #####

            //texture and transforms of the texture
            sampler2D _MainTex;
            float4 _MainTex_ST;

            //tint of the texture
            fixed4 _Color;

            //the object data that's put into the vertex shader
            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID // ##### ADDED #####
            };

            //the data that's used to generate fragments and can be read by the fragment shader
            struct v2f
            {
                float4 position : SV_POSITION;
                float4 screenPosition : TEXCOORD0;
                UNITY_VERTEX_OUTPUT_STEREO // ##### ADDED #####
            };

            //the vertex shader
            v2f vert(appdata v)
            {
                v2f o;
                // UNITY_SETUP_INSTANCE_ID() calculates and sets the built - in unity_StereoEyeIndex and unity_InstanceID Unity shader variables
                // to the correct values based on which eye the GPU is currently rendering.
                // It must be used at the very beginning of a vertex Shader
                UNITY_SETUP_INSTANCE_ID(v); // ##### ADDED #####

                // initializes all v2f values to 0
                UNITY_INITIALIZE_OUTPUT(v2f, o); // ##### ADDED #####

                // UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO tells the GPU which eye in the texture array it should render to,
                // based on the value of unity_StereoEyeIndex.This macro also transfers the value of unity_StereoEyeIndex from the vertex shader
                // so that it will be accessible in the fragment shader only if UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX is called in the fragment shader frag method.
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); // ##### ADDED #####

                // necessary only if you want to access instanced properties in the fragment Shader.
                //UNITY_TRANSFER_INSTANCE_ID(v, o);

                //convert the vertex positions from object space to clip space so they can be rendered
                o.position = UnityObjectToClipPos(v.vertex);
                o.screenPosition = ComputeScreenPos(o.position); // ComputeNonStereoScreenPos(o.position);
                return o;
            }

            //the fragment shader
            fixed4 frag(v2f i) : SV_TARGET
            {
                fixed4 col = tex2Dproj(_MainTex, i.screenPosition);
                col *= _Color;
                return col;
            }

            ENDCG
        }


        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            Tags{ "LightMode" = "ForwardBase" }
            ZWrite Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            struct v2f
            {
              
                float4 pos : SV_POSITION;
                SHADOW_COORDS(0)
                UNITY_VERTEX_OUTPUT_STEREO // ##### ADDED #####
            };

            fixed _ShadowStrength;
            v2f vert(appdata_img v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v); // ##### ADDED #####
                UNITY_INITIALIZE_OUTPUT(v2f, o); // ##### ADDED #####
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); // ##### ADDED #####

                o.pos = UnityObjectToClipPos(v.vertex);
                TRANSFER_SHADOW(o);
                return o;
            }

            fixed4 frag(v2f i) : COLOR
            {
                fixed shadow = SHADOW_ATTENUATION(i);
                fixed shadowalpha = (1.0 - shadow) * _ShadowStrength;
                return fixed4(0.0, 0.0, 0.0, shadowalpha);
            }

            ENDCG
        }



        Pass
        {
            Tags{ "LightMode" = "ShadowCaster" }

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_shadowcaster

            #include "UnityCG.cginc"

            struct v2f
            {
                V2F_SHADOW_CASTER;
                UNITY_VERTEX_OUTPUT_STEREO // ##### ADDED #####
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v); // ##### ADDED #####
                UNITY_INITIALIZE_OUTPUT(v2f, o); // ##### ADDED #####
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); // ##### ADDED #####
                TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                SHADOW_CASTER_FRAGMENT(i)
            }
            ENDCG
        }

    }
    //FallBack "Standard"
}

Setup:

Win10 / Unity 2020.2.a13 / OpenVR XR Plugin Version 1.0.0-preview.2 / HTC Vive Cosmos, Per Eye 1440 x 1700

Second Camera (as child of Main Camera under XRig):
Culling Mask → nothing / Field of View → 93.59882 (same as Main Camera) / Depth → 0 / Target Eye -->None (Main Display)

Project Settings → XR Plug-in Management → OpenVR:
App Type: Scene / Stereo Rend. Mode: Single Pass Instanced / Mirror View Mode: None

There are a number of complications you’re going to run in to here.

In non-VR, just using the screen position is useful. In your non-VR test case you have a big quad in space and you have the screen aligned to it. You can get those to line up because the screen position is centered and symmetric.

For VR it’s almost never useful since for most VR headsets the eye projections are asymmetric, usually showing slightly more to the sides than the middle, meaning a “screen position” projection won’t look like it’s infinitely far away because the center points won’t match up with “straight ahead” in both eyes. Plus screen space will never match a quad that’s anywhere in the world space, even one attached to your face, because by the nature of stereoscopic rendering anything that’s not at infinity is going to be at a different position in each eye.

Skybox rendering for VR works by rendering the sky geometry centered on each individual eye’s camera position, meaning the relative angles are identical for both eyes. It’s not using screen space.

For the main directional light’s shadows, Unity uses a screen space texture for the shadows. However it’s a unique texture per eye, rendered explicitly from that eye’s point of view, using that eye’s projection. It’s not trying to render the same texture for both eyes. The issue you’re seeing above is for some reason it’s reusing the same screen space texture for both eyes. There’s also no need to do the screen space texture and shadow in separate passes. It’ll greatly simplify things if you do both in one pass. Though I don’t entirely understand why it’s broken for you.

Bgolus, thank you for your explanations.

I’m having a hard time to understand everything, maybe even if it might be basic stuff.

Starting from your posting I would like to divide the problem in three topics:

1.) Asymmetric projection
2.) Screen space will never match a quad
3.) Directional shadows

, whereby I only focused so far on the asymmetric projection.

First, I would like to understand the theory, because I still don’t see, how to solve the problem. What I think about is to apply somehow maybe a projection matrix and eye-offset/position/orientation matrix to the target texture?

Using additional sources (
Calculating Stereo Pairs ,
https://docs.unity3d.com/ScriptReference/Camera-projectionMatrix.html?_ga=2.11977201.412277658.1594574603-2133810121.1564613509,

What do the values in the Matrix4x4 for Camera.projectionMatrix do? - Questions & Answers - Unity Discussions
) I created a drawing, with following thoughts:

  • There is an offset from the screen center to the projection center due to the asym. proj.
  • An infinite far centered object on the skybox (e.g. sun) should appear for both cameras in the projection center.
  • Four parallel rays starting at the screen corner, point in the camera view towards the projection center

The theory does not seem to fit to the next test image. Here the screen center seems to align with the projection center. But it is hard to tell, where the screen center is.

Therefore I tested the game-view in the unity editor. Here it seems that the projection center aligns for the right camera only, but not for the left.

Next image tries to interpret the last image.

Now I’m really confused.

That’s not the center line. Screen’s center line is the middle of the texture in your screen space shader.
6098031--662838--upload_2020-7-16_10-30-51.png

The “center” X geometry you have is presumably centered on the in scene camera. That should be inset towards the middle because your eyes are separated and that geometry isn’t infinitely far away. There’s also slightly more distance from the per eye forward to outside screen edge than the middle screen edge, so if you were to use a cubemap skybox that uses the grid texture for all 6 faces, the center line for that also won’t line up with the screen space center … because screen space center isn’t useful for VR.

edit: Wait, the big quad in the background is a cube map skybox, isn’t it, and the X are boxes that are just stretching out as far as they can to the far plane. So yeah, that’s showing exactly what I’m talking about then. It’s inset towards the center of the view because that’s the actual centered forward for that eye. And that does not match the screen space center, because the HMD is using asymmetric projections.

You’ve set the OpenVR settings to right eye only, but the game view is set to only show the left view… I honestly have no idea what that’ll do. I would expect it to render the non-stereo view at that point, but without seeing exactly how your scene is setup and aligned, I couldn’t say for sure what’s happening there. Generally, ignore the in editor game view for VR. It’s not the view being rendered to the eye, that’s just a debug view being rendered for the editor.

So. Solution time.

If your skybox is a cubemap already. The solution is to use the same view direction math to sample the skybox cubemap in the shader instead of trying to use screenspace.

That’s really easy.

struct v2f {
    float4 pos : SV_Position;
    float3 camRelativeWorldPos : TEXCOORD0;
};

v2f vert(appdata_full v)
{
    v2f o;

    o.pos = UnityObjectToClipPos(v.vertex);

    float3 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz;
    o.camRelativeWorldPos = worldPos - _WorldSpaceCameraPos;

    return o;
}

samplerCUBE _SkyCube;

half4 frag (v2f i) : SV_Target
{
    half4 col = texCUBE(_SkyCube, i.camRelativeWorldPos);
    return col;
}

Thank you again for the explanation.

Yes the “quad” is an image of a sykbox (but no from a cube map, but from the 6 sided version). The “X” are, as you said, parallel lines showing forward.

The OpenVR “right eye” / “left eye” setting is the only way to change the editor game view for VR. The game view settings show no effect.

The projection works now great! Thank you!!!

I removed the separate shadow passes (see code below), but what remains is the offset of the shadows in the right camera. (If I change the Stereo Rendering Mode from “Single Pass Instanced” to “Multi Pass”, than everything is ok.)

Shader "Custom/ground2"
{
    //show values to edit in inspector
    Properties{
        _Color("Tint", Color) = (0, 0, 0, 1)
        _SkyCube("Cubemap", CUBE) = "" {}
        _ShadowStrength("Shadow Strength", Range(0, 1)) = 1
    }
     
    SubShader
    {
        Tags{ "LightMode" = "ForwardBase" }

        Pass
        {
            CGPROGRAM

            //include useful shader functions
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            //define vertex and fragment shader
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            //#pragma multi_compile_instancing

            //texture and transforms of the texture
            //sampler2D _MainTex;
            //UNITY_DECLARE_SCREENSPACE_TEXTURE(_MainTex);
            //float4 _MainTex_ST;
            samplerCUBE _SkyCube;

            //tint of the texture
            fixed4 _Color;
            fixed _ShadowStrength;

            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 camRelativeWorldPos : TEXCOORD0;
                SHADOW_COORDS(1) // put shadows data into TEXCOORD1
                UNITY_VERTEX_OUTPUT_STEREO
            };

            v2f vert(appdata_full v)
            {
                v2f o;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_OUTPUT(v2f, o);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.pos = UnityObjectToClipPos(v.vertex);

                float3 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz;
                o.camRelativeWorldPos = worldPos - _WorldSpaceCameraPos;
            
                TRANSFER_SHADOW(o);

                return o;
            }
 
            half4 frag(v2f i) : SV_Target
            {
                half4 col = texCUBE(_SkyCube, i.camRelativeWorldPos);

                fixed attenuation = SHADOW_ATTENUATION(i);
                col.rgb *= (1.0f - (1.0 - attenuation) * _ShadowStrength);
                col *= _Color;
                return col;
            }

            ENDCG
        }

        // shadow casting support
        UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"

    }
    //FallBack "Standard"
}

A “cosmetic” question arises from using cubemaps in this sheader. In my game I use a (slightliy modified) 6 sided skybox shader for the Skybox material, because all available game sceneries are based on 6 separate images. I can use the Legacy Cubemap Asset (Unity - Manual: Cubemap) to generate a cubemap, which is unfortunately limited to a “face size” of 2048. I think, I can also generate the cubemap-texture myself with c#, but is it possible to use the 6 separate images in the shader, without the cubemap?

Possible? Yes.

But I’d highly recommend you use a cube map instead. It’ll save you a huge amount of hassle.

A trick you could use is to create a cube map of the resolution you want, and then use CopyTexture to copy the individual textures into the faces of the cube. Should be quite cheap to do, and then you can use the cube map for both the sky and the geometry.

Otherwise, you’ll have to implement something like a cube map lookup function that’ll get you the face and local UV from the direction vector.

I have used with success Cubemap’s SetPixels() to set the six textures.

Next problem is, that the alpha channel appears in the shader black and not transparent, but I think, that’s a limitation of the Texture2D.LoadImage function. (In the PNG’s every pixel has also a color, and the alpha is used in other similar games to set the ground object partially transparent again. This way trees with a lot of details as leafs for example cover the helicopter only where leafs have alpha == 1)}

Do you have an idea how to solve in Stereo Rendering Mode the shadow offset problem?

Not entirely sure. Possibly missing the UNITY_SETUP_INSTANCE_ID(i); at the start of the fragment shader before calling the SHADOW_ATTENTUATION macro. The screen space shadow texture gets handled differently on different VR platforms / rendering modes. Most don’t need anything more than what you have above, but some do need the instance ID in the fragment shader to be setup, so that might be it.

Adding UNITY_SETUP_INSTANCE_ID(i); at the beginning of the fragment shader leads to following error messages:

1.)
Shader error in ‘Custom/ground’: ‘UnitySetupInstanceID’: no matching 1 parameter function at line 89 (on d3d11)

Compiling Vertex program with DIRECTIONAL SHADOWS_SCREEN LIGHTPROBE_SH INSTANCING_ON
Platform defines: UNITY_ENABLE_REFLECTION_BUFFERS UNITY_USE_DITHER_MASK_FOR_ALPHABLENDED_SHADOWS UNITY_PBS_USE_BRDF1 UNITY_SPECCUBE_BOX_PROJECTION UNITY_SPECCUBE_BLENDING UNITY_ENABLE_DETAIL_NORMALMAP SHADER_API_DESKTOP UNITY_COLORSPACE_GAMMA UNITY_LIGHT_PROBE_PROXY_VOLUME UNITY_LIGHTMAP_FULL_HDR UNITY_PASS_FORWARDBASE
Disabled keywords: SHADOWS_SHADOWMASK DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING DIRLIGHTMAP_COMBINED VERTEXLIGHT_ON UNITY_NO_DXT5nm UNITY_ENABLE_NATIVE_SHADOW_LOOKUPS UNITY_METAL_SHADOWS_USE_POINT_FILTERING UNITY_NO_SCREENSPACE_SHADOWS UNITY_PBS_USE_BRDF2 UNITY_PBS_USE_BRDF3 UNITY_NO_FULL_STANDARD_SHADER UNITY_HARDWARE_TIER1 UNITY_HARDWARE_TIER2 UNITY_HARDWARE_TIER3 UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS UNITY_LIGHTMAP_DLDR_ENCODING UNITY_LIGHTMAP_RGBM_ENCODING UNITY_VIRTUAL_TEXTURING UNITY_PRETRANSFORM_TO_DISPLAY_ORIENTATION UNITY_ASTC_NORMALMAP_ENCODING

2.)
Shader error in ‘Custom/ground’: invalid subscript ‘instanceID’ at line 89 (on d3d11)

Compiling Vertex program with DIRECTIONAL SHADOWS_SCREEN LIGHTPROBE_SH INSTANCING_ON
Platform defines: UNITY_ENABLE_REFLECTION_BUFFERS UNITY_USE_DITHER_MASK_FOR_ALPHABLENDED_SHADOWS UNITY_PBS_USE_BRDF1 UNITY_SPECCUBE_BOX_PROJECTION UNITY_SPECCUBE_BLENDING UNITY_ENABLE_DETAIL_NORMALMAP SHADER_API_DESKTOP UNITY_COLORSPACE_GAMMA UNITY_LIGHT_PROBE_PROXY_VOLUME UNITY_LIGHTMAP_FULL_HDR UNITY_PASS_FORWARDBASE
Disabled keywords: SHADOWS_SHADOWMASK DYNAMICLIGHTMAP_ON LIGHTMAP_ON LIGHTMAP_SHADOW_MIXING DIRLIGHTMAP_COMBINED VERTEXLIGHT_ON UNITY_NO_DXT5nm UNITY_ENABLE_NATIVE_SHADOW_LOOKUPS UNITY_METAL_SHADOWS_USE_POINT_FILTERING UNITY_NO_SCREENSPACE_SHADOWS UNITY_PBS_USE_BRDF2 UNITY_PBS_USE_BRDF3 UNITY_NO_FULL_STANDARD_SHADER UNITY_HARDWARE_TIER1 UNITY_HARDWARE_TIER2 UNITY_HARDWARE_TIER3 UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS UNITY_LIGHTMAP_DLDR_ENCODING UNITY_LIGHTMAP_RGBM_ENCODING UNITY_VIRTUAL_TEXTURING UNITY_PRETRANSFORM_TO_DISPLAY_ORIENTATION UNITY_ASTC_NORMALMAP_ENCODING

I have added the UNITY_VERTEX_INPUT_INSTANCE_ID macro in the v2f struct,
whereby both error messages disappeared, but the shadow in the right camera are still on the wrong position.

// source:
// https://www.ronja-tutorials.com/2019/01/20/screenspace-texture.html
// https://discussions.unity.com/t/624555 ?_ga=2.69737753.803013357.1578766070-2133810121.1564613509#post-2606783
// https://discussions.unity.com/t/800101 add-reply

Shader "Custom/ground"
{
    //show values to edit in inspector
    Properties{
        _TintColor("Tint Color", Color) = (.5, .5, .5, 0.5)
        [Gamma] _Exposure("Exposure", Range(0.0, 2)) = 1.0
        _SkyCube("Cubemap", CUBE) = "" {}
        _ShadowStrength("Shadow Strength", Range(0, 1)) = 1
    }

    SubShader
    {
        Tags{ "LightMode" = "ForwardBase" }
        //Tags{ "Queue" = "Background" } //  instructs Unity to render this pass before other objects are rendered.

        Pass
        {
            CGPROGRAM

            //include useful shader functions
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"

            //define vertex and fragment shader
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fwdbase
            #pragma multi_compile_instancing

            //texture and transforms of the texture
            samplerCUBE _SkyCube;

            //tint of the texture
            half4 _TintColor;
            half _Exposure;
            fixed _ShadowStrength;


            //the object data that's put into the vertex shader
            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            //the data that's used to generate fragments and can be read by the fragment shader
            struct v2f
            {
                float4 pos : SV_POSITION;  // This is the (x, y) position of the pixel in normalized coordinates in the range (-1, -1) to (1, 1). The z is the depth position (used for the depth buffer) in the normalized range 0 to 1.
                float3 camRelativeWorldPos : TEXCOORD0;

                UNITY_VERTEX_INPUT_INSTANCE_ID // necessary only if you want to access instanced properties in fragment Shader.
                UNITY_VERTEX_OUTPUT_STEREO
                SHADOW_COORDS(1) // put shadows data into TEXCOORD1
            };

            v2f vert(appdata_full v)
            {
                v2f o;

                //UNITY_TRANSFER_INSTANCE_ID(v, o); // necessary only if you want to access instanced properties in the fragment Shader.
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_INITIALIZE_OUTPUT(v2f, o);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.pos = UnityObjectToClipPos(v.vertex);

                float3 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz;
                o.camRelativeWorldPos = worldPos - _WorldSpaceCameraPos;

                TRANSFER_SHADOW(o); // o._ShadowCoord = mul(unity_World2Shadow[0], mul(_Object2World, v.vertex));

                return o;
            }

            half4 frag(v2f i) : SV_Target
            {       
                half4 col = texCUBE(_SkyCube, i.camRelativeWorldPos);
                col *= _TintColor * _Exposure *2.0;

                // compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed)
                UNITY_SETUP_INSTANCE_ID(i); // necessary only if any instanced properties are going to be accessed in the fragment Shader.
                fixed attenuation = SHADOW_ATTENUATION(i);
                col.rgb *= (1.0f - (1.0 - attenuation) * _ShadowStrength);  
                return col;
            }

            ENDCG
        }

        // shadow casting support
        UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"

    }
    //FallBack "Standard"
}
1 Like

Not really sure what else to do. Using your shader exactly as it is above works correctly for me in VR. Adding the UNITY_SETUP_INSTANCE_ID(i) didn’t change anything since it already worked, but also didn’t cause an error. And I did check that they were rendered using single pass instancing, though not using OpenVR.

1 Like

Thank you for taking time and testing the shader. I have only a HTC VIVE Cosmos headset but I got the answer from a user, that his Oculus Rift does not have any shadow problems in my demo version (Upload files for free - Free-RC-Helicopter_Simulator_V2p0_XR_Test_001.zip - ufile.io). So it seams to be exclusively an OpenVR problem.

Yeah, I don’t have a Cosmos to test with. I do have a Vive, but I only tested it using the Rift.

One option you could try would be to use a surface shader with a custom lighting model, then if that doesn’t have any issues, look at the generated code and try to simplify it down to see what’s needed.

I did as you suggested and found that

  • in the vertex shader UNITY_TRANSFER_INSTANCE_ID(v, o); has to come after UNITY_INITIALIZE_OUTPUT(v2f, o);
    Now the shadows are correct!

I also changed your o.camRelativeWorldPos calculation, because this creates in the game a very disturbing effect: The ground - where the cubmap is projected onto - seams to be several meters below the shadow.

1.) The first change is, that “float3 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz;” is not necessary, because the ground is stationary and has no position or orientation offset.
2.) The second change is, as mentioned above, that I do not use the direction vector o.camRelativeWorldPos, but the under 1.) simplified worldPos. This is a projection of the cubmap onto the ground object from single point for both eyes. Now the ground fits perfectly into the environment. One drawback has to be mentioned: If the camera moves, the texture appears doubled behind the ground (for example behind a tree). Therefore I have to rework the 3D sceneries and create several layers with several slightly modified cubemaps.

Unfortunately the single instanced version does not reduce the GPU-load. Following figure shows two compiled runs of the game (OpenVR / VIVE Cosmos, GTX1080Ti). On the left side the GPU-load of the game with “Multi Pass”, on the right side the "Single Pass Instanced" version.

The initial problem is solved now, thank you very much bgolus!

You may be interested in this:
https://developers.google.com/vr/discover/seurat