Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Device/OS inconsistencies in GLSL ES Phong shader results

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:

with branchwithout branch

  • On my iPhone 4 running iOS 6.0, the version with the branch has a wide specular highlight (left image); without the branch I see a narrow specular highlight (right image), despite the "shininess" exponent remaining the same.
  • On the iOS 6.0 Simulator, I see the second image with both versions of the shader.
  • On my iPad (original 2010 model, stuck at iOS 5.1), I see the first image with both versions of the shader.

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!

like image 395
rickster Avatar asked Nov 12 '22 21:11

rickster


1 Answers

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.)

like image 71
rickster Avatar answered Nov 15 '22 10:11

rickster