[Oculus Go] Cubemap reflections for Single-Pass Multi-View

I’ve created a Surface shader in Unity that involves sampling from a cubemap. I am working on the Oculus Go with Single-Pass Multi-View rendering enabled. But in-game the cubemap looks as if it was just a completely flat texture applied to the surface with no stereo-depth to it(With generally “correct” cube-map samples but applied to the surface as if it was just a “_MainTex”). But when I apply a “Standard” shader on one of the meshes to the scene, the cubemap reflections appear to work where the view-direction of each eye is being accounted for during the reflection vector calculation and the reflection has proper “depth”. I’ve downloaded the built-in shaders from the unity download page to see if i can dissect if I am doing anything wrong and followed the standard shaders setup but nothing stood out to me.

The reflections appear to work fine when ran on the desktop using an Oculus rift, when Multi-Pass is enabled on the Oculus Go, or when I use one of the unity “Standard” shaders but not when I use my surface shader. Is there a flag or variable or variant flag or something I’m missing?

Here’s some key parts of my shader with some attempts to get it to retrieve stereo per-eye vectors.

    SubShader
    {
        Tags { "RenderType" = "Opaque" }
        LOD 250
        Cull Back
        CGPROGRAM
        #pragma target 4.0
        #pragma surface surf BlinnPhong vertex:vert noforwardadd nodynlightmap nofog noshadow
        #pragma multi_compile _ UNITY_SINGLE_PASS_STEREO STEREO_MULTIVIEW_ON STEREO_CUBEMAP_RENDER_ON STEREO_INSTANCING_ON
        #pragma multi_compile_instancing
        #pragma multi_compile_fwdbase
        #include "UnityCG.cginc"
...
        struct Input
        {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float2 uv_Glossiness;
            float3 worldPos;
            float worldNormal;
            float3 worldRefl;
            INTERNAL_DATA
            float3 viewDir;
            UNITY_VERTEX_OUTPUT_STEREO
        };

        void vert (inout appdata_full v, out Input o) {
            UNITY_INITIALIZE_OUTPUT(Input,o);
            UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
        }
...
        void surf (Input IN, inout SurfaceOutput o)
        {
            UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(IN)
            fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
            fixed4 glossTex = tex2D(_Glossiness, IN.uv_Glossiness);
            o.Albedo = tex.rgb;
            o.Gloss  = glossTex * _GlossinessPower;
            o.Alpha  = tex.a;
            o.Specular = _Specularity;
            o.Normal = UnpackScaleNormal(
                tex2D(_BumpMap, IN.uv_BumpMap),
                _BumpPower
            );

            const float3 ReflectionDirection = BoxProjectedCubemapDirection(
                WorldReflectionVector(IN, o.Normal).xyz,
                IN.worldPos,
                unity_SpecCube0_ProbePosition,
                unity_SpecCube0_BoxMin,
                unity_SpecCube0_BoxMax
            );

            const float4 ProbeColor = UNITY_SAMPLE_TEXCUBE_LOD(
                unity_SpecCube0,
                ReflectionDirection,
                UNITY_SPECCUBE_LOD_STEPS * (o.Gloss + _ReflectionBlurBoost)
            );

            o.Emission = ProbeColor * _ReflectionPower;
        }

I also notice that in the compiled shader in the “STEREO_MULTIVIEW_ON” variant, it seems to always use matrices from the left eye(0). Is this possibly a unity bug?

...
-- Hardware tier variant: Tier 3
-- Fragment shader for "gles3":
Shader Disassembly:
// All GLSL source is contained within the vertex program

//////////////////////////////////////////////////////
Keywords set in this variant: DIRECTIONAL STEREO_MULTIVIEW_ON
-- Hardware tier variant: Tier 1
-- Vertex shader for "gles3":
Shader Disassembly:
#ifdef VERTEX
#version 300 es
...
    u_xlat0.y = vs_TEXCOORD3.w;
    u_xlat0.z = vs_TEXCOORD4.w;
    u_xlat1.xyz = (-u_xlat0.xyz) + unity_StereoWorldSpaceCameraPos[0].xyz; // <<< It's always sampling the left eye???
    u_xlat30 = dot(u_xlat1.xyz, u_xlat1.xyz);
    u_xlat30 = inversesqrt(u_xlat30);
...

Wanted to update and say that when I compile the unity standard shader, it seems to properly get the correct camera position and uses the correct stereo index. Why is my shader not using this and how would i get it to properly work for stereo?

//////////////////////////////////////////////////////
Keywords set in this variant: DIRECTIONAL STEREO_MULTIVIEW_ON
-- Hardware tier variant: Tier 1
-- Vertex shader for "gles3":
Shader Disassembly:
#ifdef VERTEX
#version 300 es
#extension GL_OVR_multiview2 : require
...
    u_xlat0.xyz = hlslcc_mtx4x4unity_ObjectToWorld[3].xyz * in_POSITION0.www + u_xlat0.xyz;
    u_xlatu9 = gl_ViewID_OVR; // < Why isnt my surface shader using this?
    vs_TEXCOORD1.xyz = u_xlat0.xyz + (-unity_StereoWorldSpaceCameraPos[int(u_xlatu9)].xyz);
    vs_TEXCOORD8.xyz = u_xlat0.xyz;
    vs_BLENDWEIGHT0 = unity_StereoEyeIndices[int(u_xlatu9)].x; // << Gets the correct eye?

I don’t know the answer — I’m terrible at shaders — but wanted to make sure you’ve seen this page of the docs. Might there be something helpful there?

I practically have this page on my bookmarks and just about every tutorial out there seems to go right to going to totally custom shaders rather than standard/surface shaders.
I feel like this may be a bug on unity’s part as the consistent compiled-shader output seems to always use stereo index 0 when compiling multi-view shaders but I did find a way to bypass this issue by passing in the world camera position to the fragment shader by adding another field in the input struct and conditionally writing the correct camera position to it from within the vertex shader.

        struct Input
        {
...
            float3 WorldCameraPos;
        };

        void vert (inout appdata_full v, out Input o) {
...
            #if STEREO_MULTIVIEW_ON
            o.WorldCameraPos = unity_StereoWorldSpaceCameraPos[unity_StereoEyeIndex];
            #else
            o.WorldCameraPos = _WorldSpaceCameraPos.xyz;
            #endif
...

        void surf (Input IN, inout SurfaceOutput o)
        {
...
            const float3 worldRefl = -reflect(
                IN.WorldCameraPos - IN.worldPos.xyz,
                WorldNormalVector(IN,o.Normal)
            );
...
        }
1 Like