5

Apple 的 OpenGL ES 最佳实践建议不要对片段着色器中计算的结果进行分支。但是Phong 着色通常涉及当光源位于表面的“错误”一侧时跳过镜面反射项,对此直接的方法是点单位法线方向N和光方向L并检查是否为正结果。

我试图在我的着色器中没有分支的情况下做到这一点:我没有使用if语句,而是对镜面反射项进行所有计算,然后给它一个系数,1.0如果dot(N, L)大于零,0.0否则。(我使用内置step()函数实现了这一点。组合max()sign()产生相同的结果,但据说速度有点慢。)

然而,这似乎会导致奇怪的、特定于设备和/或 iOS 版本的结果:

带分支无分支

  • 在我运行 iOS 6.0 的 iPhone 4 上,带有分支的版本具有宽高光(左图);如果没有分支,我会看到一个狭窄的镜面高光(右图),尽管“光泽度”指数保持不变。
  • 在 iOS 6.0 模拟器上,我看到了带有两个版本的着色器的第二张图像。
  • 在我的 iPad(最初的 2010 型号,卡在 iOS 5.1)上,我看到了带有两个版本的着色器的第一张图像。

显然,问题不在于分支或缺乏分支。(顺便说一下,右边的图像是“正确的”渲染。)

这是我的片段着色器:

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);
}

我也欢迎提供进一步的建议,以提高此着色器在 iOS 硬件上的性能!

4

1 回答 1

1

正如@BradLarson 的评论中所指出的,lowp限定符 onmaterialShininess原来是问题所在;相反mediump,它会在我手头的所有设备和操作系统版本上正确渲染(右侧图像),无论使用的是分支版本还是无分支(带有step)版本的着色器。

(对输入使用lowpvs并从中计算不会产生任何明显的差异,这是有道理的:这些是​​归一化的向量,因此它们的分量的幅度在 0.0 到 1.0 的范围内。这与颜色分量的范围相同,对于似乎是故意的。)mediumpRVcos_alphalowp

于 2013-02-13T18:35:45.987 回答