Surface shaders not rendering as expected on Android (Oculus Quest)

Both shaders are surfaces shaders, Standard lighting, fullforwardshadows (disabled on Android though) and target 3.5. Both use instancing and texture arrays.

I have two problems:

One shader is overlit (too reflective/glossy).

The other uses a perlin map for randomization. On Quest, it creates artifacts (looks like its lower res) and close up you can see that a part seems to be missing (texture not being sampled on either end of the perlin noise?).

I fiddled with quality settings but none had any effect so I suppose it’s a shader problem.

Here are screenshots from the editor, showing how it’s supposed to look:

5511925--565558--editor01.png
The texture on the bricks is visible, not overlit.
5511925--565561--editor02.png
The terrain doesn’t have artifacts, no gaps.

Here’s how it looks on the device:

The texture on the brick is hardly visible, totally overlit.


There are gaps on one part of the perlin noise and there are visible seams.

Any help would be appreciated, thanks.

Change
fixed2 uv_MainTex;
to
float2 uv_MainTex;

and do the same for the second UV set. This will help get rid of seams.

For the textures, check quality settings.

I changed fixed to float for the UVs, thanks. Will test later and check back if it helped with the seams.

These are my current quality settings:


I already tried different settings for the anisotropic textures (it was off originally). Other than that, I cycled through different presets, starting with medium going up to ultra. None of the settings I tried had any effect on the two problems I’m having.

Also, the gaps on the terrain shader should be caused by the shader, shouldn’t they? I mean it’s the same texture that is sampled twice, only the second time offset and mirrored. But for some reason only one side of the lerp is showing the texture.

Check, which settings are used on Android, those are probably “Medium” by default.
The look on the textures can be different due to different texture compression (it’s ETC/ETC2 by default for Android).

Gaps are likely a result of precision issues - desktop GPUs normally handle everything as full floats, but on mobile half is supported natively.

I tested the shaders with float instead of fixed and that completely solved problem #2 (with the terrain). So far so good, thanks!

The quality settings are the ones in the screenshot (green checkmark, and the pull down menu on the downward facing triangle has my custom layer selected). Texture compression is ASTC (recommended by Oculus), I also tried with no override originally and my first attempt to fix the problems was to switch to ASTC. Without override it should have been the compression used on desktop.

It’s not that clear from the screenshot in the OP, but the textures aren’t overlit everywhere, more around the center of the camera (maybe some kind of specular effect?). Here’s another screenshot that should better illustrate the problem:


I have the same highlight on desktop, but much smaller and less dull. After switching the platform to Android, it turned dull in the editor but it isn’t that huge so it doesn’t cover most of the near view. On the actual Android device it really is a problem since you can’t tell the color of the tile bricks anymore.

There’s another thing called Graphics Tiers. Can you check those?

By default Android will use Tier 2. Can you try and set the Standard Shader Quality to High?

That fixed it, many thanks!

1 Like

I have a quite similar problem where I have a custom shader I created that works perfectly on my PC and also through Link, but when building and installing the apk in the Quest, it doesn’t work anymore.

Unfortunately for me that solution didn’t work.

To be more specific the shader works if I don’t try to use any data coming from the tex2D function as return value for my frag function. Actually I realized I can use the function, but I can’t use any data coming from there in the data that will be returned by frag, even if it works in my desktop and through Link.

The code to the custom shader follows:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/SphericalMaskDissolve1" {
    Properties{
        _TintColor("Tint Color", Color) = (0.5,0.5,0.5,0.5)
        _MainTex("Particle Texture", 2D) = "white" {}
        _InvFade("Soft Particles Factor", Range(0.01,3.0)) = 1.0

        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
        [HDR]_Emission("Emission", Color) = (1,1,1,1)
        _NoiseSize("Noise size", float) = 1
    }

        Category{
            Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
            Blend SrcAlpha One
            AlphaTest Greater .01
            ColorMask RGB
            Cull Off Lighting Off ZWrite Off Fog { Color(0,0,0,0) }
            BindChannels {
                Bind "Color", color
                Bind "Vertex", vertex
                Bind "TexCoord", texcoord
            }

            // ---- Fragment program cards
            SubShader {
                Pass {

                    CGPROGRAM
                    #pragma vertex vert
                    #pragma fragment frag
                    #pragma fragmentoption ARB_precision_hint_fastest
                    #pragma multi_compile_particles

                    #include "UnityCG.cginc"

                    sampler2D _MainTex;
                    fixed4 _TintColor;

                    half _Glossiness;
                    half _Metallic;

                    fixed4 _Emission;
                    float _NoiseSize;

                    float3 _GLOBALMaskPosition;
                    half _GLOBALMaskRadius;
                    half _GLOBALMaskSoftness;

                    struct appdata_t {
                        float4 vertex : POSITION;
                        fixed4 color : COLOR;
                        float2 texcoord : TEXCOORD0;

                        float4 position : POSITION;//
                    };

                    struct v2f {
                        float4 vertex : POSITION;
                        fixed4 color : COLOR;
                        float2 texcoord : TEXCOORD0;
                        float2 texcoord2 : TEXCOORD0;
                        float4 worldSpacePos : TEXCOORD1;//
                        #ifdef SOFTPARTICLES_ON
                        float4 projPos : TEXCOORD1;
                        #endif
                    };

                    struct Input {
                        float2 uv_MainTex;
                        float3 worldPos;
                    };

                    float4 _MainTex_ST;

                    v2f vert(appdata_t v)
                    {
                        v2f o;
                        o.vertex = UnityObjectToClipPos(v.vertex);
                        #ifdef SOFTPARTICLES_ON
                        o.projPos = ComputeScreenPos(o.vertex);
                        COMPUTE_EYEDEPTH(o.projPos.z);
                        #endif
                        o.color = v.color;
                        o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);

                        o.worldSpacePos = mul(unity_ObjectToWorld, v.position);//
                        return o;
                    }

                    sampler2D _CameraDepthTexture;
                    float _InvFade;

                    fixed4 frag(v2f i) : COLOR
                    {
                        #ifdef SOFTPARTICLES_ON
                        float sceneZ = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));
                        float partZ = i.projPos.z;
                        float fade = saturate(_InvFade * (sceneZ - partZ));
                        i.color.a *= fade;
                        #endif

                        half dist = distance(_GLOBALMaskPosition, i.worldSpacePos);//
                        half sphere = 1 - saturate((dist - _GLOBALMaskRadius) / _GLOBALMaskSoftness);//
                        //clip(sphere - 0.1);//

                        float4 textureColor = tex2D(_MainTex, i.texcoord);

                        return 2.0f * i.color * _TintColor * textureColor;
                    }


                    ENDCG
                }
            }

            // ---- Dual texture cards
            SubShader {
                Pass {
                    SetTexture[_MainTex] {
                        constantColor[_TintColor]
                        combine constant * primary
                    }
                    SetTexture[_MainTex] {
                        combine texture * previous DOUBLE
                    }
                }
            }

                        // ---- Single texture cards (does not do color tint)
                        SubShader {
                            Pass {
                                SetTexture[_MainTex] {
                                    combine texture * primary
                                }
                            }
                        }
        }
}

If I don’t use “textureColor” in the return line of frag (even if storing it to a variable) it works the same way on Quest as on PC

@edufireheart no, it’s a completely different problem.
Try doing all you computations in float and check if this helps.

Indeed it is a different problem but I was seeing it as not having a shader behaving the same way on Quest as it does on PC or through Oculus Link.

Unfortunately the result is the same.

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/SphericalMaskDissolve1" {
    Properties{
        _TintColor("Tint Color", Color) = (0.5,0.5,0.5,0.5)
        _MainTex("Particle Texture", 2D) = "white" {}
        _InvFade("Soft Particles Factor", Range(0.01,3.0)) = 1.0

        _Glossiness("Smoothness", Range(0,1)) = 0.5
        _Metallic("Metallic", Range(0,1)) = 0.0
        [HDR]_Emission("Emission", Color) = (1,1,1,1)
        _NoiseSize("Noise size", float) = 1
    }

        Category{
            Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
            Blend SrcAlpha One
            AlphaTest Greater .01
            ColorMask RGB
            Cull Off Lighting Off ZWrite Off Fog { Color(0,0,0,0) }
            BindChannels {
                Bind "Color", color
                Bind "Vertex", vertex
                Bind "TexCoord", texcoord
            }

            // ---- Fragment program cards
            SubShader {
                Pass {

                    CGPROGRAM
                    #pragma vertex vert
                    #pragma fragment frag
                    #pragma fragmentoption ARB_precision_hint_fastest
                    #pragma multi_compile_particles

                    #include "UnityCG.cginc"

                    sampler2D _MainTex;
                    fixed4 _TintColor;

                    half _Glossiness;
                    half _Metallic;

                    fixed4 _Emission;
                    float _NoiseSize;

                    float3 _GLOBALMaskPosition;
                    half _GLOBALMaskRadius;
                    half _GLOBALMaskSoftness;

                    struct appdata_t {
                        float4 vertex : POSITION;
                        //fixed4 color : COLOR;
                        float4 color : COLOR;
                        float2 texcoord : TEXCOORD0;

                        float4 position : POSITION;//
                    };

                    struct v2f {
                        float4 vertex : POSITION;
                        //fixed4 color : COLOR;
                        float4 color : COLOR;
                        float2 texcoord : TEXCOORD0;
                        float2 texcoord2 : TEXCOORD0;
                        float4 worldSpacePos : TEXCOORD1;//
                        #ifdef SOFTPARTICLES_ON
                        float4 projPos : TEXCOORD1;
                        #endif
                    };

                    struct Input {
                        float2 uv_MainTex;
                        float3 worldPos;
                    };

                    float4 _MainTex_ST;

                    v2f vert(appdata_t v)
                    {
                        v2f o;
                        o.vertex = UnityObjectToClipPos(v.vertex);
                        #ifdef SOFTPARTICLES_ON
                        o.projPos = ComputeScreenPos(o.vertex);
                        COMPUTE_EYEDEPTH(o.projPos.z);
                        #endif
                        o.color = v.color;
                        o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);

                        o.worldSpacePos = mul(unity_ObjectToWorld, v.position);//
                        return o;
                    }

                    sampler2D _CameraDepthTexture;
                    float _InvFade;


                    float4 frag(v2f i) : COLOR
                    {
                        #ifdef SOFTPARTICLES_ON
                        float sceneZ = LinearEyeDepth(UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));
                        float partZ = i.projPos.z;
                        float fade = saturate(_InvFade * (sceneZ - partZ));
                        i.color.a *= fade;
                        #endif

                        float4 textureColor = tex2D(_MainTex, i.texcoord);

                        //float4 temp = float4(textureColor.r, 1.0, 1.0, 1.0);



                        float4 temp = i.color;

                        temp.r = 1.0;
                        temp.g = 0.0;
                        temp.b = 0.0;

                        //temp.a = textureColor.a;

                        return temp;
                    }   

                    ENDCG
                }
            }

            // ---- Dual texture cards
            SubShader {
                Pass {
                    SetTexture[_MainTex] {
                        constantColor[_TintColor]
                        combine constant * primary
                    }
                    SetTexture[_MainTex] {
                        combine texture * previous DOUBLE
                    }
                }
            }

                        // ---- Single texture cards (does not do color tint)
                        SubShader {
                            Pass {
                                SetTexture[_MainTex] {
                                    combine texture * primary
                                }
                            }
                        }
        }
}

In the code here I tried to change the color into a float4 and made it red in the frag function for testing purposes. When I uncomment the line with temp.a = textureColor.a; the shader doesn’t behave anymore the same way it does in the Editor. Actually the result is as it took the texture and applied the _TintColor to it, instead of just being red as the frag function tells it to do.

Is it something I’m getting wrong? Even if it is, it’s quite strange to see it behaving well in the Editor and through Oculus Link (a simple red shader for testing) or on Quest when not returning values from tex2D but having _TintColor instead when having anything from tex2D in the returned value.

From the code I shared above (only for testing purposes) I expected a red shader.

How I see in the Editor and through Oculus Link:

How I see it on the Quest after I hit Build And Run:


(this happens whenever I use any value from tex2D as part of the returned value of the frag function)

Do you mind filing a bug report with this?

Ok, I’ll do that.

Thanks for the help