Forum Discussion

🚨 This forum is archived and read-only. To submit a forum post, please visit our new Developer Forum. 🚨
TippingPoint's avatar
TippingPoint
Honored Guest
6 years ago

[Unity] Single-Pass Stereo GrabPass or Post Processing for Android/ Oculus Quest

I'm
currently working in Unity 2018.3.10 on converting an Oculus Rift application to the
Oculus Quest, and have run into an issue with post processing and
grabpass shaders.

I have a gaussian blur effect, and i have two versions that both work fine on the oculus rift, in windows, and in the editor.

The
first version is a 3-pass shader using GrabPass to iterate on the
previous result, applied to an inverted sphere that surrounds the
player. This way is convenient because it allows me to take advantage of
z-order to draw some UI on top of the sphere and blur what's behind,
and because i can use this to blur sheets of glass and such things.

When
viewed in the Oculus Quest, no blur effect is visible, and the sphere
is only visible as a black circle, and is only visible in the left eye.
When i get close to the sphere and enter it, that sphere turns white.

Here's the code for that material:

[code=CSharp]// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unlit/GrabPassGaussBlurOnTop" {
    Properties {
        _MainTex ("Tint Color (RGB)", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
        _Tint ("_Tint", Color) = (0, 0, 0, 0)
        _BumpAmt  ("Distortion", Range (0,128)) = 10
        _BumpMap ("Normalmap", 2D) = "bump" {}
        _Size ("Size", Range(0, 20)) = 1
    }
 
    Category {
 
        // We must be transparent, so other objects are drawn before this one.
        Tags { "Queue"="Transparent+1" "IgnoreProjector"="True" "RenderType"="Opaque" }
        ZWrite Off
        ZTest Always
     
        SubShader {
    
            // Horizontal blur
            GrabPass { "_BlurGrab"             
                Tags { "LightMode" = "Always" }
            }
            Pass {
                Tags { "LightMode" = "Always" }
            
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                #include "UnityCG.cginc"
            
                struct appdata_t {
                    float4 vertex : POSITION;
                    float2 texcoord: TEXCOORD0;
                };
            
                struct v2f {
                    float4 vertex : POSITION;
                    float4 uvgrab : TEXCOORD0;
                    UNITY_VERTEX_OUTPUT_STEREO
                };
            
                v2f vert (appdata_t v) {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                    // #if UNITY_UV_STARTS_AT_TOP
                    // float scale = -1.0;
                    // #else
                    // float scale = 1.0;
                    // #endif
                    // o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
                    // o.uvgrab.zw = o.vertex.zw;
                    o.uvgrab = ComputeGrabScreenPos(o.vertex);
                    return o;
                }
            
                UNITY_DECLARE_SCREENSPACE_TEXTURE(_BlurGrab);
                float4 _BlurGrab_TexelSize;
                float _Size;
                fixed4 _Color;

                half4 frag( v2f i ) : COLOR {
//                  half4 col = tex2Dproj( _BlurGrab, UNITY_PROJ_COORD(i.uvgrab));
//                  return col;
                
                    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
                    half4 sum = half4(0,0,0,0);
  
                 #define GRABPIXEL(weight,kernelx)
UNITY_SAMPLE_SCREENSPACE_TEXTURE( _BlurGrab,
UNITY_PROJ_COORD(float4(i.uvgrab.x + _BlurGrab_TexelSize.x *
kernelx*_Size*_Color[3], i.uvgrab.y, i.uvgrab.z,
i.uvgrab.w))/i.uvgrab.w) * weight
                    sum += GRABPIXEL(0.05, -4.0);
                    sum += GRABPIXEL(0.09, -3.0);
                    sum += GRABPIXEL(0.12, -2.0);
                    sum += GRABPIXEL(0.15, -1.0);
                    sum += GRABPIXEL(0.18,  0.0);
                    sum += GRABPIXEL(0.15, +1.0);
                    sum += GRABPIXEL(0.12, +2.0);
                    sum += GRABPIXEL(0.09, +3.0);
                    sum += GRABPIXEL(0.05, +4.0);
                
                    return sum;
                }
                ENDCG
            }
            // Vertical blur
            GrabPass { "_BlurGrab1"                  
                Tags { "LightMode" = "Always" }
            }
            Pass {
                Tags { "LightMode" = "Always" }
            
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                #include "UnityCG.cginc"
            
                struct appdata_t {
                    float4 vertex : POSITION;
                    float2 texcoord: TEXCOORD0;
                };
            
                struct v2f {
                    float4 vertex : POSITION;
                    float4 uvgrab : TEXCOORD0;
                    UNITY_VERTEX_OUTPUT_STEREO
                };
            
                v2f vert (appdata_t v) {
                    v2f o;
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uvgrab = ComputeGrabScreenPos(o.vertex);
                    return o;
                }
            
                UNITY_DECLARE_SCREENSPACE_TEXTURE(_BlurGrab1);
                float4 _BlurGrab1_TexelSize;
                float _Size;
                fixed4 _Color;

                half4 frag( v2f i ) : COLOR {
//                  half4 col = tex2Dproj( _BlurGrab1, UNITY_PROJ_COORD(i.uvgrab));
//                  return col;
                
                    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
                    half4 sum = half4(0,0,0,0);
  
                 #define GRABPIXEL(weight,kernely)
UNITY_SAMPLE_SCREENSPACE_TEXTURE( _BlurGrab1,
UNITY_PROJ_COORD(float4(i.uvgrab.x, i.uvgrab.y + _BlurGrab1_TexelSize.y *
kernely*_Size*_Color[3], i.uvgrab.z, i.uvgrab.w))/i.uvgrab.w) * weight
                    //G(X) = (1/(sqrt(2*PI*deviation*deviation))) * exp(-(x*x / (2*deviation*deviation)))
                
                    sum += GRABPIXEL(0.05, -4.0);
                    sum += GRABPIXEL(0.09, -3.0);
                    sum += GRABPIXEL(0.12, -2.0);
                    sum += GRABPIXEL(0.15, -1.0);
                    sum += GRABPIXEL(0.18,  0.0);
                    sum += GRABPIXEL(0.15, +1.0);
                    sum += GRABPIXEL(0.12, +2.0);
                    sum += GRABPIXEL(0.09, +3.0);
                    sum += GRABPIXEL(0.05, +4.0);
                
                    return sum;
                }
                ENDCG
            }
        
            // Distortion
            GrabPass { "_BlurGrab2"                     
                Tags { "LightMode" = "Always" }
            }
            Pass {
                Tags { "LightMode" = "Always" }
            
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma fragmentoption ARB_precision_hint_fastest
                #include "UnityCG.cginc"
            
                struct appdata_t {
                    float4 vertex : POSITION;
                    float2 texcoord: TEXCOORD0;
                };
            
                struct v2f {
                    float4 vertex : POSITION;
                    float4 uvgrab : TEXCOORD0;
                    float2 uvbump : TEXCOORD1;
                    float2 uvmain : TEXCOORD2;
                    UNITY_VERTEX_OUTPUT_STEREO
                };
            
                float _BumpAmt;
                float4 _BumpMap_ST;
                float4 _MainTex_ST;
            
                v2f vert (appdata_t v) {
                    v2f o;
                    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uvgrab = ComputeGrabScreenPos(o.vertex);
                    o.uvbump = TRANSFORM_TEX( v.texcoord, _BumpMap );
                    o.uvmain = TRANSFORM_TEX( v.texcoord, _MainTex );
                    return o;
                }
            
                fixed4 _Color;
                UNITY_DECLARE_SCREENSPACE_TEXTURE(_BlurGrab2);
                float4 _BlurGrab2_TexelSize;
                sampler2D _BumpMap;
                sampler2D _MainTex;
                fixed4 _Tint;
            
                half4 frag( v2f i ) : COLOR {
                    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
                    // calculate perturbed coordinates
  
                 half2 bump = UnpackNormal(tex2D( _BumpMap, i.uvbump
)).rg; // we could optimize this by just reading the x  y without
reconstructing the Z
                    float2 offset = bump * _BumpAmt * _BlurGrab2_TexelSize.xy;
                    i.uvgrab.xy = offset * i.uvgrab.z + i.uvgrab.xy;
                    float4 projCoords = UNITY_PROJ_COORD(i.uvgrab);
                    half4 col = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_BlurGrab2, projCoords.xy/projCoords.w);
                    half4 color = tex2D( _MainTex, i.uvmain ) * _Color;
                
                    return col * color + _Tint;
                }
                ENDCG
            }
        }
    }
}

The
other version is a custom post process for PPV2. It blits from the
previous result to a new buffer 3 times, once for each gaussian blur
pass (horizontal, vertical, and final blend).

When
viewed in the Oculus Quest, no blur is visible, and the entire left eye
is grey. Here's the post process shader for that one:

[code=CSharp]Shader "Custom/GaussianBlur"
{
    HLSLINCLUDE

        //  use the first include in 2018.2, and the second line in 2018.3.
        // #include "PostProcessing/Shaders/StdLib.hlsl"
        #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
        #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/xRLib.hlsl"

        TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
        float _Size;
        float _ScreenHeight, _ScreenWidth;
        float _LeftEyeInvisible, _RightEyeInvisible;

        float4 Frag(VaryingsDefault i) : SV_Target
        {
            i.texcoord = TransformStereoScreenSpaceTex(i.texcoord, 1);
            float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
            return color;
        }
     
        float4 FragHoriz(VaryingsDefault i) : SV_Target
        {
            half eye = step(.5, i.texcoordStereo.x);
            clip(eye - _LeftEyeInvisible * .1);
            clip(-_RightEyeInvisible * eye);

            // float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
            half4 sum = half4(0,0,0,0);
            i.texcoord = TransformStereoScreenSpaceTex(i.texcoord, 1);
            float2 texelSize = float2(1/_ScreenWidth, 1/_ScreenHeight);

  
         #define GRABPIXELX(weight,kernelx) SAMPLE_TEXTURE2D( _MainTex,
sampler_MainTex, float2(i.texcoord.x + texelSize.x * kernelx*_Size,
i.texcoord.y)) * weight

            sum += GRABPIXELX(0.05, -4.0);
            sum += GRABPIXELX(0.09, -3.0);
            sum += GRABPIXELX(0.12, -2.0);
            sum += GRABPIXELX(0.15, -1.0);
            sum += GRABPIXELX(0.18,  0.0);
            sum += GRABPIXELX(0.15, +1.0);
            sum += GRABPIXELX(0.12, +2.0);
            sum += GRABPIXELX(0.09, +3.0);
            sum += GRABPIXELX(0.05, +4.0);
         
            return sum;
        }

        float4 FragVertical(VaryingsDefault i) : SV_Target
        {
            half eye = step(.5, i.texcoordStereo.x);
            clip(eye - _LeftEyeInvisible * .1);
            clip(-_RightEyeInvisible * eye);
            // float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
            half4 sum = half4(0,0,0,0);
            i.texcoord = TransformStereoScreenSpaceTex(i.texcoord, 1);

            float2 texelSize = float2(1/_ScreenWidth, 1/_ScreenHeight);
            // texelSize = float2(1, 1);
         
  
         #define GRABPIXELY(weight,kernely) SAMPLE_TEXTURE2D( _MainTex,
sampler_MainTex, float2(i.texcoord.x, i.texcoord.y + texelSize.y *
kernely*_Size)) * weight

            sum += GRABPIXELY(0.05, -4.0);
            sum += GRABPIXELY(0.09, -3.0);
            sum += GRABPIXELY(0.12, -2.0);
            sum += GRABPIXELY(0.15, -1.0);
            sum += GRABPIXELY(0.18,  0.0);
            sum += GRABPIXELY(0.15, +1.0);
            sum += GRABPIXELY(0.12, +2.0);
            sum += GRABPIXELY(0.09, +3.0);
            sum += GRABPIXELY(0.05, +4.0);
         
            return sum;
        }

    ENDHLSL

    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            HLSLPROGRAM

                #pragma vertex VertDefault
                #pragma fragment FragHoriz

            ENDHLSL
        }
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            HLSLPROGRAM

                #pragma vertex VertDefault
                #pragma fragment FragVertical

            ENDHLSL
        }
     
        Pass
        {
            HLSLPROGRAM

                #pragma vertex VertDefault
                #pragma fragment Frag

            ENDHLSL
        }
    }
}

As you can see in the first shader, I attempted to apply the adjustments described in this unity manual page: https://docs.unity3d.com/Manual/Android-SinglePassStereoRendering.html
but
to no avail. The page doesn't mention GrabPass at all, and it isn't
clear if grabpass is even implemented at all for Android Single-Pass.
Additionally, the grab-pass sampling is normally tex2Dproj, but the
screenspace-texture-array sampling macro provided uses tex2D, so i had
to perform the perspective divide myself. The lack of a tex2Dproj
alternative indicates that unity may not have considered GrabPass when
adjusting for android single pass.

The
Blurs both worth in Multi-Pass on the Quest, but performance using
multi-pass is unnacceptably bad, so I'm willing to do a lot to avoid
that.

Thank you for any help!

Edit:
I am well aware of how bad of an idea post processing and grabpasses
and blurs are on mobile. This is for a client and i am unable at this
point to change the design, which absolutely needs working blur.


No RepliesBe the first to reply