Apple's Best Practices for OpenGL ES recommend against branching on results calculated in a fragment shader. But Phong shading generally involves skipping the specular term when the light source is on the "wrong" side of the surface, for which the straightforward approach is to dot the unit normal direction N
and light direction L
and check for a positive result.
I attempted to do this without a branch in my shader: instead of using an if
statement, I do all the calculations for the specular term and then give it a coefficient which is 1.0
if dot(N, L)
is greater than zero and 0.0
otherwise. (I achieve this using the builtin step()
function. Combining max()
and sign()
produces the same results but is said to be a bit slower.)
However, this appeared to lead to odd, device- and/or iOS version-specific, results:
Clearly, it's not the branching or lack thereof that's the problem. (The right-hand image is the "correct" rendering, by the way.)
Here's my fragment shader:
precision mediump float;
uniform lowp vec3 ambientLight;
uniform lowp vec3 light0Color;
uniform lowp vec3 materialAmbient;
uniform lowp vec3 materialDiffuse;
uniform lowp vec3 materialSpecular;
uniform lowp float materialShininess;
// input variables from vertex shader (in view coordinates)
varying vec3 NormDir;
varying vec3 ViewDir;
varying vec3 LightDir;
void main (void)
{
vec3 L = normalize(LightDir);
vec3 N = normalize(NormDir);
vec3 E = normalize(ViewDir);
lowp vec3 ambient = ambientLight * materialAmbient;
float cos_theta = dot(L, N);
lowp vec3 diffuse = materialDiffuse * light0Color * max(0.0, cos_theta);
lowp vec3 specular = vec3(0.0, 0.0, 0.0);
// if (cos_theta > 0.0) {
lowp vec3 R = reflect(-L, N);
lowp vec3 V = normalize(-E);
float cos_alpha = dot(R, V);
specular = step(0.0, cos_theta) * materialSpecular * light0Color * pow(max(0.0, cos_alpha), materialShininess);
// ~~~~~~~~~~~~~~~~~~~~~~
// should produce the same results as the commented branch, right?
// }
gl_FragColor = vec4(ambient + diffuse + specular, 1.0);
}
I welcome further suggestions for improving this shader's performance on iOS hardware, too!
As noted in @BradLarson's comment, the lowp
qualifier on materialShininess
turned out to be the problem; with that set to mediump
instead, it renders correctly (right-hand image) on all devices and OS versions I have on hand, regardless of whether the branch or no-branch (with step
) version of the shader is used.
(Using lowp
versus mediump
for the inputs R
and V
from which cos_alpha
is calculated doesn't make any visible difference, which makes sense: those are normalized vectors, so their components have magnitudes in the range 0.0 to 1.0. That's the same range as color components, for which lowp
seems to be intended.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With