Search Unity

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

Discussion in 'Image Effects' started by swanickj, May 30, 2019.

  1. swanickj

    swanickj

    Joined:
    May 15, 2019
    Posts:
    28
    I'm currently working 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):
    1. // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
    2.  
    3. Shader "Unlit/GrabPassGaussBlurOnTop" {
    4.     Properties {
    5.         _MainTex ("Tint Color (RGB)", 2D) = "white" {}
    6.         _Color ("Color", Color) = (1,1,1,1)
    7.         _Tint ("_Tint", Color) = (0, 0, 0, 0)
    8.         _BumpAmt  ("Distortion", Range (0,128)) = 10
    9.         _BumpMap ("Normalmap", 2D) = "bump" {}
    10.         _Size ("Size", Range(0, 20)) = 1
    11.     }
    12.  
    13.     Category {
    14.  
    15.         // We must be transparent, so other objects are drawn before this one.
    16.         Tags { "Queue"="Transparent+1" "IgnoreProjector"="True" "RenderType"="Opaque" }
    17.         ZWrite Off
    18.         ZTest Always
    19.      
    20.         SubShader {
    21.    
    22.             // Horizontal blur
    23.             GrabPass { "_BlurGrab"            
    24.                 Tags { "LightMode" = "Always" }
    25.             }
    26.             Pass {
    27.                 Tags { "LightMode" = "Always" }
    28.            
    29.                 CGPROGRAM
    30.                 #pragma vertex vert
    31.                 #pragma fragment frag
    32.                 #pragma fragmentoption ARB_precision_hint_fastest
    33.                 #include "UnityCG.cginc"
    34.            
    35.                 struct appdata_t {
    36.                     float4 vertex : POSITION;
    37.                     float2 texcoord: TEXCOORD0;
    38.                 };
    39.            
    40.                 struct v2f {
    41.                     float4 vertex : POSITION;
    42.                     float4 uvgrab : TEXCOORD0;
    43.                     UNITY_VERTEX_OUTPUT_STEREO
    44.                 };
    45.            
    46.                 v2f vert (appdata_t v) {
    47.                     v2f o;
    48.                     o.vertex = UnityObjectToClipPos(v.vertex);
    49.                     UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    50.                     // #if UNITY_UV_STARTS_AT_TOP
    51.                     // float scale = -1.0;
    52.                     // #else
    53.                     // float scale = 1.0;
    54.                     // #endif
    55.                     // o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
    56.                     // o.uvgrab.zw = o.vertex.zw;
    57.                     o.uvgrab = ComputeGrabScreenPos(o.vertex);
    58.                     return o;
    59.                 }
    60.            
    61.                 UNITY_DECLARE_SCREENSPACE_TEXTURE(_BlurGrab);
    62.                 float4 _BlurGrab_TexelSize;
    63.                 float _Size;
    64.                 fixed4 _Color;
    65.  
    66.                 half4 frag( v2f i ) : COLOR {
    67. //                  half4 col = tex2Dproj( _BlurGrab, UNITY_PROJ_COORD(i.uvgrab));
    68. //                  return col;
    69.                
    70.                     UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
    71.                     half4 sum = half4(0,0,0,0);
    72.                     #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
    73.                     sum += GRABPIXEL(0.05, -4.0);
    74.                     sum += GRABPIXEL(0.09, -3.0);
    75.                     sum += GRABPIXEL(0.12, -2.0);
    76.                     sum += GRABPIXEL(0.15, -1.0);
    77.                     sum += GRABPIXEL(0.18,  0.0);
    78.                     sum += GRABPIXEL(0.15, +1.0);
    79.                     sum += GRABPIXEL(0.12, +2.0);
    80.                     sum += GRABPIXEL(0.09, +3.0);
    81.                     sum += GRABPIXEL(0.05, +4.0);
    82.                
    83.                     return sum;
    84.                 }
    85.                 ENDCG
    86.             }
    87.             // Vertical blur
    88.             GrabPass { "_BlurGrab1"                  
    89.                 Tags { "LightMode" = "Always" }
    90.             }
    91.             Pass {
    92.                 Tags { "LightMode" = "Always" }
    93.            
    94.                 CGPROGRAM
    95.                 #pragma vertex vert
    96.                 #pragma fragment frag
    97.                 #pragma fragmentoption ARB_precision_hint_fastest
    98.                 #include "UnityCG.cginc"
    99.            
    100.                 struct appdata_t {
    101.                     float4 vertex : POSITION;
    102.                     float2 texcoord: TEXCOORD0;
    103.                 };
    104.            
    105.                 struct v2f {
    106.                     float4 vertex : POSITION;
    107.                     float4 uvgrab : TEXCOORD0;
    108.                     UNITY_VERTEX_OUTPUT_STEREO
    109.                 };
    110.            
    111.                 v2f vert (appdata_t v) {
    112.                     v2f o;
    113.                     UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    114.                     o.vertex = UnityObjectToClipPos(v.vertex);
    115.                     o.uvgrab = ComputeGrabScreenPos(o.vertex);
    116.                     return o;
    117.                 }
    118.            
    119.                 UNITY_DECLARE_SCREENSPACE_TEXTURE(_BlurGrab1);
    120.                 float4 _BlurGrab1_TexelSize;
    121.                 float _Size;
    122.                 fixed4 _Color;
    123.  
    124.                 half4 frag( v2f i ) : COLOR {
    125. //                  half4 col = tex2Dproj( _BlurGrab1, UNITY_PROJ_COORD(i.uvgrab));
    126. //                  return col;
    127.                
    128.                     UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
    129.                     half4 sum = half4(0,0,0,0);
    130.                     #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
    131.                     //G(X) = (1/(sqrt(2*PI*deviation*deviation))) * exp(-(x*x / (2*deviation*deviation)))
    132.                
    133.                     sum += GRABPIXEL(0.05, -4.0);
    134.                     sum += GRABPIXEL(0.09, -3.0);
    135.                     sum += GRABPIXEL(0.12, -2.0);
    136.                     sum += GRABPIXEL(0.15, -1.0);
    137.                     sum += GRABPIXEL(0.18,  0.0);
    138.                     sum += GRABPIXEL(0.15, +1.0);
    139.                     sum += GRABPIXEL(0.12, +2.0);
    140.                     sum += GRABPIXEL(0.09, +3.0);
    141.                     sum += GRABPIXEL(0.05, +4.0);
    142.                
    143.                     return sum;
    144.                 }
    145.                 ENDCG
    146.             }
    147.        
    148.             // Distortion
    149.             GrabPass { "_BlurGrab2"                    
    150.                 Tags { "LightMode" = "Always" }
    151.             }
    152.             Pass {
    153.                 Tags { "LightMode" = "Always" }
    154.            
    155.                 CGPROGRAM
    156.                 #pragma vertex vert
    157.                 #pragma fragment frag
    158.                 #pragma fragmentoption ARB_precision_hint_fastest
    159.                 #include "UnityCG.cginc"
    160.            
    161.                 struct appdata_t {
    162.                     float4 vertex : POSITION;
    163.                     float2 texcoord: TEXCOORD0;
    164.                 };
    165.            
    166.                 struct v2f {
    167.                     float4 vertex : POSITION;
    168.                     float4 uvgrab : TEXCOORD0;
    169.                     float2 uvbump : TEXCOORD1;
    170.                     float2 uvmain : TEXCOORD2;
    171.                     UNITY_VERTEX_OUTPUT_STEREO
    172.                 };
    173.            
    174.                 float _BumpAmt;
    175.                 float4 _BumpMap_ST;
    176.                 float4 _MainTex_ST;
    177.            
    178.                 v2f vert (appdata_t v) {
    179.                     v2f o;
    180.                     UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    181.                     o.vertex = UnityObjectToClipPos(v.vertex);
    182.                     o.uvgrab = ComputeGrabScreenPos(o.vertex);
    183.                     o.uvbump = TRANSFORM_TEX( v.texcoord, _BumpMap );
    184.                     o.uvmain = TRANSFORM_TEX( v.texcoord, _MainTex );
    185.                     return o;
    186.                 }
    187.            
    188.                 fixed4 _Color;
    189.                 UNITY_DECLARE_SCREENSPACE_TEXTURE(_BlurGrab2);
    190.                 float4 _BlurGrab2_TexelSize;
    191.                 sampler2D _BumpMap;
    192.                 sampler2D _MainTex;
    193.                 fixed4 _Tint;
    194.            
    195.                 half4 frag( v2f i ) : COLOR {
    196.                     UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
    197.                     // calculate perturbed coordinates
    198.                     half2 bump = UnpackNormal(tex2D( _BumpMap, i.uvbump )).rg; // we could optimize this by just reading the x  y without reconstructing the Z
    199.                     float2 offset = bump * _BumpAmt * _BlurGrab2_TexelSize.xy;
    200.                     i.uvgrab.xy = offset * i.uvgrab.z + i.uvgrab.xy;
    201.                     float4 projCoords = UNITY_PROJ_COORD(i.uvgrab);
    202.                     half4 col = UNITY_SAMPLE_SCREENSPACE_TEXTURE(_BlurGrab2, projCoords.xy/projCoords.w);
    203.                     half4 color = tex2D( _MainTex, i.uvmain ) * _Color;
    204.                
    205.                     return col * color + _Tint;
    206.                 }
    207.                 ENDCG
    208.             }
    209.         }
    210.     }
    211. }
    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):
    1. Shader "Custom/GaussianBlur"
    2. {
    3.     HLSLINCLUDE
    4.  
    5.         //  use the first include in 2018.2, and the second line in 2018.3.
    6.         // #include "PostProcessing/Shaders/StdLib.hlsl"
    7.         #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
    8.         #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/xRLib.hlsl"
    9.  
    10.         TEXTURE2D_SAMPLER2D(_MainTex, sampler_MainTex);
    11.         float _Size;
    12.         float _ScreenHeight, _ScreenWidth;
    13.         float _LeftEyeInvisible, _RightEyeInvisible;
    14.  
    15.         float4 Frag(VaryingsDefault i) : SV_Target
    16.         {
    17.             i.texcoord = TransformStereoScreenSpaceTex(i.texcoord, 1);
    18.             float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
    19.             return color;
    20.         }
    21.      
    22.         float4 FragHoriz(VaryingsDefault i) : SV_Target
    23.         {
    24.             half eye = step(.5, i.texcoordStereo.x);
    25.             clip(eye - _LeftEyeInvisible * .1);
    26.             clip(-_RightEyeInvisible * eye);
    27.  
    28.             // float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
    29.             half4 sum = half4(0,0,0,0);
    30.             i.texcoord = TransformStereoScreenSpaceTex(i.texcoord, 1);
    31.             float2 texelSize = float2(1/_ScreenWidth, 1/_ScreenHeight);
    32.  
    33.             #define GRABPIXELX(weight,kernelx) SAMPLE_TEXTURE2D( _MainTex, sampler_MainTex, float2(i.texcoord.x + texelSize.x * kernelx*_Size, i.texcoord.y)) * weight
    34.  
    35.             sum += GRABPIXELX(0.05, -4.0);
    36.             sum += GRABPIXELX(0.09, -3.0);
    37.             sum += GRABPIXELX(0.12, -2.0);
    38.             sum += GRABPIXELX(0.15, -1.0);
    39.             sum += GRABPIXELX(0.18,  0.0);
    40.             sum += GRABPIXELX(0.15, +1.0);
    41.             sum += GRABPIXELX(0.12, +2.0);
    42.             sum += GRABPIXELX(0.09, +3.0);
    43.             sum += GRABPIXELX(0.05, +4.0);
    44.          
    45.             return sum;
    46.         }
    47.  
    48.         float4 FragVertical(VaryingsDefault i) : SV_Target
    49.         {
    50.             half eye = step(.5, i.texcoordStereo.x);
    51.             clip(eye - _LeftEyeInvisible * .1);
    52.             clip(-_RightEyeInvisible * eye);
    53.             // float4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.texcoord);
    54.             half4 sum = half4(0,0,0,0);
    55.             i.texcoord = TransformStereoScreenSpaceTex(i.texcoord, 1);
    56.  
    57.             float2 texelSize = float2(1/_ScreenWidth, 1/_ScreenHeight);
    58.             // texelSize = float2(1, 1);
    59.          
    60.             #define GRABPIXELY(weight,kernely) SAMPLE_TEXTURE2D( _MainTex, sampler_MainTex, float2(i.texcoord.x, i.texcoord.y + texelSize.y * kernely*_Size)) * weight
    61.  
    62.             sum += GRABPIXELY(0.05, -4.0);
    63.             sum += GRABPIXELY(0.09, -3.0);
    64.             sum += GRABPIXELY(0.12, -2.0);
    65.             sum += GRABPIXELY(0.15, -1.0);
    66.             sum += GRABPIXELY(0.18,  0.0);
    67.             sum += GRABPIXELY(0.15, +1.0);
    68.             sum += GRABPIXELY(0.12, +2.0);
    69.             sum += GRABPIXELY(0.09, +3.0);
    70.             sum += GRABPIXELY(0.05, +4.0);
    71.          
    72.             return sum;
    73.         }
    74.  
    75.     ENDHLSL
    76.  
    77.     SubShader
    78.     {
    79.         Cull Off ZWrite Off ZTest Always
    80.  
    81.         Pass
    82.         {
    83.             HLSLPROGRAM
    84.  
    85.                 #pragma vertex VertDefault
    86.                 #pragma fragment FragHoriz
    87.  
    88.             ENDHLSL
    89.         }
    90.         Cull Off ZWrite Off ZTest Always
    91.  
    92.         Pass
    93.         {
    94.             HLSLPROGRAM
    95.  
    96.                 #pragma vertex VertDefault
    97.                 #pragma fragment FragVertical
    98.  
    99.             ENDHLSL
    100.         }
    101.      
    102.         Pass
    103.         {
    104.             HLSLPROGRAM
    105.  
    106.                 #pragma vertex VertDefault
    107.                 #pragma fragment Frag
    108.  
    109.             ENDHLSL
    110.         }
    111.     }
    112. }
    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.

    Edit: it's 7 months later and i'm checking in on this thread, realizing i never specified what version of unity this was in. I think that it was 2018.3.
     
    Last edited: Jan 20, 2020
  2. WACOMalt

    WACOMalt

    Joined:
    Jul 17, 2012
    Posts:
    11
    Sorry to reply with no solution, but I too am hitting this wall with similar shader work. Just replying to say Me too!
     
    swanickj likes this.
  3. WhiteNoiz

    WhiteNoiz

    Joined:
    Nov 30, 2012
    Posts:
    2
  4. GeorgeAdamon

    GeorgeAdamon

    Joined:
    May 31, 2017
    Posts:
    48
  5. Mandelboxed

    Mandelboxed

    Joined:
    Apr 17, 2015
    Posts:
    50
    Last edited: Jun 19, 2019
  6. jeromeWork

    jeromeWork

    Joined:
    Sep 1, 2015
    Posts:
    429
    Same here on Unity 2018.4.1f1 Oculus Quest. Builds with Post processing have left eye rendered just grey
     
  7. AlessandroElifani

    AlessandroElifani

    Joined:
    Jan 1, 2017
    Posts:
    4
  8. tinnystudios

    tinnystudios

    Joined:
    Oct 27, 2016
    Posts:
    18
  9. yonek_idreams

    yonek_idreams

    Joined:
    Sep 10, 2014
    Posts:
    26
  10. GCCooper

    GCCooper

    Joined:
    Apr 25, 2018
    Posts:
    19
  11. hungrybelome

    hungrybelome

    Joined:
    Dec 31, 2014
    Posts:
    336
  12. FloHal

    FloHal

    Joined:
    Feb 25, 2013
    Posts:
    38
  13. YuriyPopov

    YuriyPopov

    Joined:
    Sep 5, 2017
    Posts:
    237
  14. Epic_Cube

    Epic_Cube

    Joined:
    Jul 3, 2012
    Posts:
    100
  15. lhaughFVR

    lhaughFVR

    Joined:
    Dec 9, 2019
    Posts:
    2
    Ensure that any shaders used by the post processing are in the always include shader list, this helped me get it working.
     
  16. swanickj

    swanickj

    Joined:
    May 15, 2019
    Posts:
    28
    by 'it' working, do you mean your own grabpass effects, or my example? and can you confirm this is in Android VR using Single-Pass Stereo Rendering? what version of unity is it working for you in
     
    jeromeWork likes this.
  17. Jonas-Neuston

    Jonas-Neuston

    Joined:
    Jun 10, 2017
    Posts:
    70
  18. Matiasbru

    Matiasbru

    Joined:
    Sep 1, 2015
    Posts:
    20
    Unity 2019.4 LTS using URP on Oculus Quest 2.... get black on both eyes. Unity editor freezing as well.
     
  19. Alex_Heizenrader

    Alex_Heizenrader

    Joined:
    May 16, 2019
    Posts:
    95
  20. creat327

    creat327

    Joined:
    Mar 19, 2009
    Posts:
    1,756
    +1 since it's been only 2 years waiting for a fix
     
  21. VoxelMatt

    VoxelMatt

    Joined:
    Apr 22, 2015
    Posts:
    42
    For anyone wondering, this seems to be fixed.
    You just have to turn off
    Optimize Buffer Discards (Vulkan)
    in Project Settings -> XR -> Oculus
     
    jeromeWork likes this.