I'm trying to combine an ordinary shader with a Single Distance Field shader (for fonts and scalable icons) into one shader program. The idea is simple - if uv.x is less than 1.0, it is drawn as usual, and if more than 1.0 then SDF is used (using GL_REPEAT uv.x and uv.x + 1.0 look similar)
Fragment shader code:
precision mediump float;
precision mediump int;
uniform sampler2D u_texture;
varying vec4 v_color;
varying vec2 v_texCoords;
uniform float u_smoothing;
void main() {
// SDF
float distance = texture2D(u_texture, v_texCoords).a;
float alpha = smoothstep(0.5 - u_smoothing, 0.5 + u_smoothing, distance);
vec4 sdfColor = vec4(v_color.rgb, alpha * v_color.a);
// Regular
vec4 regularColor = v_color * texture2D(u_texture, v_texCoords);
// Making a choice
gl_FragColor = mix(regularColor, sdfColor, step(1.0, v_texCoords.x));
}
It works and looks fine on desktop and most of popular Android devices but not on Oneplus One, Xiaomi Redmi 4A and Moto G devices.
This is how it must look like and how it actually looks on desktops and many other devices (Samsung S8, Xiaomi 3s, Pixel 2 etc.):
And this is how it looks like on Moto G, Oneplus One & some others:
I tried to find a problematic function, but without success. First I tried to use the usual condition instead of mix():
void main() {
// ...
// Making a choice
// gl_FragColor = mix(regularColor, sdfColor, step(1.0, v_texCoords.x));
if (v_texCoords.x < 1.0) {
gl_FragColor = regularColor;
} else {
gl_FragColor = sdfColor;
}
}
Nothing has changed - this shader works well on most devices and is still bad on some others. I decided that the problem lies in the calculation of sdfColor and changed fragment shader code a bit to check:
void main() {
// ...
// Making a choice
gl_FragColor = sdfColor; // Always using SDF
}
But this shader worked equally on all devices, which indicates that sdfColor is calculated correctly. This next screenshot was taken on Moto G:
Then I tried to differentiate the regions of textures with different colors:
void main() {
// ...
// Making a choice
if (v_texCoords.x < 1.0) {
gl_FragColor = regularColor;
gl_FragColor.r = 1.0; // Full red channel for regular texture regions without SDF (UV.x < 1.0)
} else {
gl_FragColor = sdfColor;
gl_FragColor.r = 0.0; // No red channel for SDF
}
}
On Moto G and other devices it looks as expected:
It seems to me that using mix() or conditions implicitly changes something else.
Update 1:
Using different precision qualifier (highp / lowp) or completely removing it didn't help and made no visual changes.
This bug starts to appear on desktop (OpenGL) if I add #version 100
. Changing it to #version 110
or higher returns normal rendering.
When #version 100
is used on Android (OpenGL ES 2.0) it renders as if there were no #version
qualifier at all (exactly the same as described in my question - on Nexus 5x it looks good, on Moto G it doesn't).
Using #version 300 es
on Android (OpenGL ES 3.0, looks like it is supported Moto G 3rd) with respective changes to the code (uniform, varying, texture2D replaced by in, out, texture) doesn't fix the problem (good on Nexus, bad on Moto).
Precision qualifier was not used in both cases. Looks like #version
does no effect to this problem on Android but makes it appear on desktop when set to 100 (is it OpenGL 1.0?...)
Update 2:
Writing v_texCoords.x
to the separate float
and using it in mix()
fixes this bug but only for desktop when #version 100
is also used:
#version 100
uniform sampler2D u_texture;
varying vec4 v_color;
varying vec2 v_texCoords;
uniform float u_smoothing;
void main() {
float x = v_texCoords.x; // Writing to the separate float
// SDF
float distance = texture2D(u_texture, v_texCoords).a;
float alpha = smoothstep(0.5 - u_smoothing, 0.5 + u_smoothing, distance);
vec4 sdfColor = vec4(v_color.rgb, alpha * v_color.a);
// Regular
vec4 regularColor = v_color * texture2D(u_texture, v_texCoords);
// Making a choice
gl_FragColor = mix(regularColor, sdfColor, step(1.0, x)); // Using x instead of v_texCoords.x
}
It doesn't fix the problem on Moto G. Also when I move float x = v_texCoords.x;
to the line right before gl_FragColor = ...
the problem remains on desktop:
void main() {
// SDF
float distance = texture2D(u_texture, v_texCoords).a;
float alpha = smoothstep(0.5 - u_smoothing, 0.5 + u_smoothing, distance);
vec4 sdfColor = vec4(v_color.rgb, alpha * v_color.a);
// Regular
vec4 regularColor = v_color * texture2D(u_texture, v_texCoords);
// Making a choice
float x = v_texCoords.x; // Writing to the separate float, this time it doesn't fix the bug
gl_FragColor = mix(regularColor, sdfColor, step(1.0, x)); // Using x instead of v_texCoords.x
}
highp
instead ofmediump
, or get rid of the precision qualifiers as recommended for more modern versions of OpenGL ES. Trylowp
and see if you see any noticable difference as well and try doing the same for int. Maybe try making the code even simpler for the compiler by always assigning first before using members, its possible the GLSL compiler is bugged on those systems. Also you should probably specify the GLSL version with#version
. \$\endgroup\$