Why does not my custom material works on Android?

I made a PBR shader, but it looked not good, so I tried to mix the PBR and the Blinn-Phong lighting, awesome ! it unexpectedly looked great on Windows, but it lost Blinn-Phong lighting effect on Android. After my test, the calculated normal (TBN * var_normal) in PBR is okay, but in Blinn-Phong is bad, if I substituted var_normal for the calculated normal in Blinn-Phong, then looked normal, but it lost bump details.
On Windows :


On Android :

substituted var_normal for the calculated normal in Blinn-Phong on Android :

The custom standard.fp :

#extension GL_OES_standard_derivatives : enable

varying highp vec4 var_frag_pos;
varying highp vec3 var_normal;
varying highp vec2 var_texcoord0;
varying highp vec3 var_spherical_harmonic_lighting;

uniform highp vec4 view_pos;
uniform highp vec4 roughness;

uniform highp vec4 directional_light_dir;
uniform highp vec4 directional_light_ambient;
uniform highp vec4 directional_light_diffuse;
uniform highp vec4 directional_light_specular;

uniform highp vec4 point_light_pos;
uniform highp vec4 point_light_ambient;
uniform highp vec4 point_light_diffuse;
uniform highp vec4 point_light_specular;

uniform highp vec4 spot_light_pos;
uniform highp vec4 spot_light_ambient;
uniform highp vec4 spot_light_diffuse;
uniform highp vec4 spot_light_specular;
uniform highp vec4 spot_light_inout_cut_off;  //x is cut off, y is outter cut off, z and w are unused.

uniform highp vec4 light_attenuation_params;  //x is constant, y is linear, z is quadratic, w is unused.
uniform highp vec4 tint;

uniform highp sampler2D diffuse_map;
uniform highp sampler2D metallic_map;
uniform highp sampler2D normal_map;
uniform highp sampler2D ao_map;

const float exposure = 1.0;

const float PI = 3.14159265359;

vec3 get_normal_from_map()
{
    vec3 tangent_normal = texture2D(normal_map, var_texcoord0).xyz * 2.0 - 1.0;

    vec3 Q1  = dFdx(vec3(var_frag_pos));
    vec3 Q2  = dFdy(vec3(var_frag_pos));
    vec2 st1 = dFdx(vec2(var_texcoord0));
    vec2 st2 = dFdy(vec2(var_texcoord0));

    vec3 N   = normalize(var_normal);
    vec3 T  = normalize(Q1*st2.t - Q2*st1.t);
    vec3 B  = -normalize(cross(N, T));
    mat3 TBN = mat3(T, B, N);

    return normalize(TBN * tangent_normal);
}

float distribution_GGX(vec3 N, vec3 H, float roughness)
{
    float a = roughness * roughness;
    float a2 = a * a;
    float NdotH = max(dot(N, H), 0.0);
    float NdotH2 = NdotH * NdotH;

    float nom = a2;
    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = PI * denom * denom;

    return nom / denom;
}

float geometry_schlick_GGX(float NdotV, float roughness)
{
    float r = (roughness + 1.0);
    float k = (r * r) / 8.0;

    float nom = NdotV;
    float denom = NdotV * (1.0 - k) + k;

    return nom / denom;
}

float geometry_smith(vec3 N, vec3 V, vec3 L, float roughness)
{
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    float ggx2 = geometry_schlick_GGX(NdotV, roughness);
    float ggx1 = geometry_schlick_GGX(NdotL, roughness);

    return ggx1 * ggx2;
}

vec3 fresnel_schlick(float cosTheta, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}

vec4 Albedo(vec2 uv)
{
    vec2 resolution = vec2(2048, 2048);
    vec4 color1 = texture2D(diffuse_map, uv + (vec2(-1.0,-1.0)/resolution.xy), -1.0);
    vec4 color2 = texture2D(diffuse_map, uv + (vec2(1.0,-1.0)/resolution.xy), -1.0);
    vec4 color3 = texture2D(diffuse_map, uv + (vec2(-1.0,1.0)/resolution.xy), -1.0);
    vec4 color4 = texture2D(diffuse_map, uv + (vec2(1.0,1.0)/resolution.xy), -1.0);
    vec4 color5 = texture2D(diffuse_map, uv + (vec2(-1.0,0.0)/resolution.xy), -1.0);
    vec4 color6 = texture2D(diffuse_map, uv + (vec2(1.0,0.0)/resolution.xy), -1.0);
    vec4 color7 = texture2D(diffuse_map, uv + (vec2(0.0,-1.0)/resolution.xy), -1.0);
    vec4 color8 = texture2D(diffuse_map, uv + (vec2(0.0,1.0)/resolution.xy), -1.0);
    vec4 color = (color1 + color2 + color3 + color4 + color5 + color6 + color7 + color8) / 8.0;
    return color;
}

vec3 calculate_directional_light(vec3 dir, vec3 normal, vec3 view_dir, vec3 frag_color)
{
    vec3 light_dir = normalize(-dir);
    // diffuse shading
    float diff = max(dot(normal, light_dir), 0.0);
    // specular shading
    vec3 halfway_dir = normalize(light_dir + view_dir);
    float spec = pow(max(dot(normal, halfway_dir), 0.0), 32.0);
    // combine results
    vec3 ambient = vec3(directional_light_ambient) * frag_color;
    vec3 diffuse = vec3(directional_light_diffuse) * diff * frag_color * var_spherical_harmonic_lighting;
    vec3 specular = vec3(directional_light_specular) * spec * frag_color;
    return (ambient + diffuse + specular);
}

vec3 calculate_point_light(vec3 light_pos, vec3 normal, vec3 frag_pos, vec3 view_dir, vec3 frag_color)
{
    vec3 light_dir = normalize(light_pos - frag_pos);
    // diffuse shading
    float diff = max(dot(normal, light_dir), 0.0);
    // specular shading
    vec3 halfway_dir = normalize(light_dir + view_dir);
    float spec = pow(max(dot(normal, halfway_dir), 0.0), 32.0);
    // attenuation
    float distance = length(light_pos - frag_pos);
    float constant = light_attenuation_params.x;
    float linear = light_attenuation_params.y;
    float quadratic = light_attenuation_params.z;
    float attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance));
    // combine results
    vec3 ambient = vec3(point_light_ambient) * frag_color;
    vec3 diffuse = vec3(point_light_diffuse) * diff * frag_color;
    vec3 specular = vec3(point_light_specular) * spec * frag_color;
    ambient = ambient * attenuation;
    diffuse = diffuse * attenuation;
    specular = specular * attenuation;
    return (ambient + diffuse + specular);
}

vec3 calculate_spot_light(vec3 light_pos, vec3 normal, vec3 frag_pos, vec3 view_dir ,vec3 frag_color)
{
    vec3 light_dir = normalize(light_pos - frag_pos);
    // diffuse shading
    float diff = max(dot(normal, light_dir), 0.0);
    // specular shading
    vec3 halfway_dir = normalize(light_dir + view_dir);
    float spec = pow(max(dot(normal, halfway_dir), 0.0), 32.0);
    // attenuation
    float distance = length(light_pos - frag_pos);
    float constant = light_attenuation_params.x;
    float linear = light_attenuation_params.y;
    float quadratic = light_attenuation_params.z;
    float attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance));
    // spotlight intensitys
    float theta = dot(light_dir, normalize(vec3(light_pos.x, light_pos.y, light_pos.z)));
    float cut_off = spot_light_inout_cut_off.x;
    float outter_cut_off = spot_light_inout_cut_off.y;
    float epsilon = cut_off - outter_cut_off;
    float intensity = clamp((theta - outter_cut_off) / epsilon, 0.0, 1.0);
    // combine results
    vec3 ambient = vec3(spot_light_ambient) * frag_color;
    vec3 diffuse = vec3(spot_light_diffuse) * diff * frag_color;
    vec3 specular = vec3(spot_light_specular) * spec * frag_color;
    //var_spherical_harmonic_lighting
    ambient = ambient * attenuation * intensity;
    diffuse = diffuse * attenuation * intensity;
    specular = specular * attenuation * intensity;
    return (ambient + diffuse + specular);
}

void main()
{
    vec3 view_dir = normalize(vec3(view_pos - var_frag_pos));
    vec3 frag_color = vec3(Albedo(var_texcoord0.xy));

    vec3 albedo = pow(frag_color, vec3(2.2));
    float metallic = texture2D(metallic_map, var_texcoord0.xy).r;
    float ao = texture2D(ao_map, var_texcoord0.xy).r;

    vec3 N = get_normal_from_map();
    vec3 V = normalize(vec3(view_pos - var_frag_pos));

    vec3 F0 = vec3(0.04);
    F0 = mix(F0, albedo, metallic);

    vec3 Lo = vec3(0.0);
    vec3 L = normalize(vec3(spot_light_pos - var_frag_pos));
    vec3 H = normalize(V + L);
    float distance = length(spot_light_pos - var_frag_pos);
    float attenuation = 1.0 / (distance * distance);

    float theta = dot(L, normalize(vec3(spot_light_pos)));
    float cut_off = spot_light_inout_cut_off.x;
    float outter_cut_off = spot_light_inout_cut_off.y;
    float epsilon = cut_off - outter_cut_off;
    float intensity = clamp((theta - outter_cut_off) / epsilon, 0.0, 1.0);

    vec3 radiance = vec3(spot_light_diffuse * attenuation * intensity);

    float NDF = distribution_GGX(N, H, roughness.x);
    float G = geometry_smith(N, V, L, roughness.x);
    vec3 F = fresnel_schlick(max(dot(H, V), 0.0), F0);

    vec3 nominator = NDF * G * F;
    float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001;
    vec3 specular = nominator / denominator;

    vec3 kS = F;
    vec3 kD = vec3(1.0) - kS;
    kD *= 1.0 - metallic;

    float NdotL = max(dot(N, L), 0.0);

    Lo += (kD * albedo / PI + specular) * radiance * NdotL;

    vec3 ambient = vec3(0.05) * albedo * ao;

    vec3 color = ambient + Lo;

    color += calculate_spot_light(vec3(spot_light_pos), N, vec3(var_frag_pos), view_dir, frag_color);

    color = color / (color + vec3(1.0));

    color = pow(color, vec3(1.0 / 2.2));

    gl_FragColor = vec4(color, 1.0) * tint;
}

@britzl @Pkeod

2 Likes

FYI, to take a screenshot from your device, directly onto your computer, you can use Android Studio.

3 Likes

Some Android devices allow pressing the power button and volume down buttons together to take a screenshot.

The issue is most likely differences in GLES features supported between the desktop and Android devices. It could be an error in the shader as well. Does it look the same as desktop if you make an HTML5 build?

1 Like

Yes, I build HTML5 on Windows, it looks good. I guess that is about the calculate result of normal is different between the Windows and Android, but the code is same.

Can you minimize the code to include just the problem area? I can take a quick look.

OK.

#extension GL_OES_standard_derivatives : enable

varying highp vec4 var_frag_pos;
varying highp vec3 var_normal;
varying highp vec2 var_texcoord0;
varying highp vec3 var_spherical_harmonic_lighting;

uniform highp vec4 view_pos;
uniform highp vec4 roughness;

uniform highp vec4 directional_light_dir;
uniform highp vec4 directional_light_ambient;
uniform highp vec4 directional_light_diffuse;
uniform highp vec4 directional_light_specular;

uniform highp vec4 point_light_pos;
uniform highp vec4 point_light_ambient;
uniform highp vec4 point_light_diffuse;
uniform highp vec4 point_light_specular;

uniform highp vec4 spot_light_pos;
uniform highp vec4 spot_light_ambient;
uniform highp vec4 spot_light_diffuse;
uniform highp vec4 spot_light_specular;
uniform highp vec4 spot_light_inout_cut_off;  //x is cut off, y is outter cut off, z and w are unused.

uniform highp vec4 light_attenuation_params;  //x is constant, y is linear, z is quadratic, w is unused.
uniform highp vec4 tint;

uniform highp sampler2D diffuse_map;
uniform highp sampler2D metallic_map;
uniform highp sampler2D normal_map;
uniform highp sampler2D ao_map;

const float exposure = 1.0;

const float PI = 3.14159265359;

vec3 get_normal_from_map()
{
    vec3 tangent_normal = texture2D(normal_map, var_texcoord0).xyz * 2.0 - 1.0;

    vec3 Q1  = dFdx(vec3(var_frag_pos));
    vec3 Q2  = dFdy(vec3(var_frag_pos));
    vec2 st1 = dFdx(var_texcoord0);
    vec2 st2 = dFdy(var_texcoord0);

    vec3 N   = normalize(var_normal);
    vec3 T  = normalize(Q1*st2.y - Q2*st1.y);
    vec3 B  = -normalize(cross(N, T));
    mat3 TBN = mat3(T, B, N);

    return normalize(TBN * tangent_normal);
}

vec4 Albedo(vec2 uv)
{
    vec2 resolution = vec2(2048, 2048);
    vec4 color1 = texture2D(diffuse_map, uv + (vec2(-1.0,-1.0)/resolution.xy), -1.0);
    vec4 color2 = texture2D(diffuse_map, uv + (vec2(1.0,-1.0)/resolution.xy), -1.0);
    vec4 color3 = texture2D(diffuse_map, uv + (vec2(-1.0,1.0)/resolution.xy), -1.0);
    vec4 color4 = texture2D(diffuse_map, uv + (vec2(1.0,1.0)/resolution.xy), -1.0);
    vec4 color5 = texture2D(diffuse_map, uv + (vec2(-1.0,0.0)/resolution.xy), -1.0);
    vec4 color6 = texture2D(diffuse_map, uv + (vec2(1.0,0.0)/resolution.xy), -1.0);
    vec4 color7 = texture2D(diffuse_map, uv + (vec2(0.0,-1.0)/resolution.xy), -1.0);
    vec4 color8 = texture2D(diffuse_map, uv + (vec2(0.0,1.0)/resolution.xy), -1.0);
    vec4 color = (color1 + color2 + color3 + color4 + color5 + color6 + color7 + color8) / 8.0;
    return color;
}

vec3 calculate_spot_light(vec3 light_pos, vec3 normal, vec3 frag_pos, vec3 view_dir ,vec3 frag_color)
{
    vec3 light_dir = normalize(light_pos - frag_pos);
    // diffuse shading
    float diff = max(dot(normal, light_dir), 0.0);
    // specular shading
    vec3 halfway_dir = normalize(light_dir + view_dir);
    float spec = pow(max(dot(normal, halfway_dir), 0.0), 32.0);
    // attenuation
    float distance = length(light_pos - frag_pos);
    float constant = light_attenuation_params.x;
    float linear = light_attenuation_params.y;
    float quadratic = light_attenuation_params.z;
    float attenuation = 1.0 / (constant + linear * distance + quadratic * (distance * distance));
    // spotlight intensitys
    float theta = dot(light_dir, normalize(vec3(light_pos.x, light_pos.y, light_pos.z)));
    float cut_off = spot_light_inout_cut_off.x;
    float outter_cut_off = spot_light_inout_cut_off.y;
    float epsilon = cut_off - outter_cut_off;
    float intensity = clamp((theta - outter_cut_off) / epsilon, 0.0, 1.0);
    // combine results
    vec3 ambient = vec3(spot_light_ambient) * frag_color;
    vec3 diffuse = vec3(spot_light_diffuse) * diff * frag_color;
    vec3 specular = vec3(spot_light_specular) * spec * frag_color;
    //var_spherical_harmonic_lighting
    ambient = ambient * attenuation * intensity;
    diffuse = diffuse * attenuation * intensity;
    specular = specular * attenuation * intensity;
    return (ambient + diffuse + specular);
}

void main()
{
    vec3 view_dir = normalize(vec3(view_pos - var_frag_pos));
    vec3 frag_color = vec3(Albedo(var_texcoord0.xy));

    vec3 N = get_normal_from_map();

    vec3 color = vec3(0.0);

    color += calculate_spot_light(vec3(spot_light_pos), N, vec3(var_frag_pos), view_dir, frag_color);

    gl_FragColor = vec4(color, 1.0) * tint;
}