Шейдер выглядит ужасно на iOS, но прекрасно работает на Windows

Я работаю над кроссплатформенным рендерером для Windows и iOS. Для примера приложения я написал шейдер, который без проблем работал на обеих платформах (версия шейдера «#версия 120» в Windows и «#версия 100» в iOS).

Далее я добавил еще два источника света и на iOS начали происходить странные вещи. Я несколько раз переделывал свой код, но он так и не показал ожидаемого результата (факел рендерится с помощью шейдера только с одним источником света, поэтому на iOS он выглядит нормально).

(1) Иногда всё было одного цвета:

введите здесь описание изображения

(2) А иногда это выглядело просто ужасно в любом случае:

введите здесь описание изображения

В Windows это выглядит так, как я ожидал:

введите здесь описание изображения

Кто-нибудь знает, почему мой шейдер делает это на iOS?

Это мой фрагментный шейдер для (2):

//Light
uniform mediump float  numLights;
//L0
uniform mediump vec4   lightPosition_0;
uniform mediump vec3   lightColor_0;
uniform mediump float  lightIntensity_0;
uniform mediump float  lightAttenuation_0;
//L1
/*same as for light 0 */
//L2
/*same as for light 0 */

uniform mediump vec3    ambientColor;

uniform mediump vec3 Ka;
uniform mediump vec3 Kd;
uniform mediump vec3 Ks;

uniform sampler2D NormalMap;
uniform sampler2D DiffuseMap;
uniform sampler2D SpecularMap;

varying highp vec4 texCoordVarying;

varying highp vec4 posWorldSpace;       // pos in View Space

varying highp vec3 tangentSurface2light_0;
varying highp vec3 tangentSurface2light_1;
varying highp vec3 tangentSurface2light_2;

void main()
{
    //normal based on normal map
    highp vec3 normalNormalMap = normalize(texture2D(NormalMap, texCoordVarying.st).xyz *2.0 - 1.0);

    //ambient light
    highp vec3 ambient  = clamp(ambientColor + Ka, 0.0, 1.0);

    //diffuse light
    highp vec3 diffuse;

    // light 0
    if(numLights >= 1.0)
    {
        //light intensity based on distance from light to object
        highp float distance = distance(posWorldSpace, lightPosition_0);
        highp float intensityBasedOnDist = lightIntensity_0/(lightAttenuation_0*distance*distance);
        intensityBasedOnDist = clamp(intensityBasedOnDist, 0.0, 1.0);

        //light intensity based on angle between normal and light vector
        highp float intensity = max(dot(normalNormalMap,tangentSurface2light_0), 0.0) ;
        intensity = clamp(intensity, 0.0, 1.0);

        //diffuse light
        diffuse  += lightColor_0 * intensity * intensityBasedOnDist;
    }
    // light 1
    if(numLights >= 2.0)
    {
        /*same as for light 0 */
    }
    // light 2
    if(numLights >= 3.0)
    {
       /*same as for light 0 */
    }

    diffuse = diffuse * Kd * texture2D(DiffuseMap, texCoordVarying.st).xyz;

    gl_FragColor = vec4(clamp(ambient + diffuse, 0.0, 1.0), 1.0);
}

Я сделал много вещей highp или mediump. Когда я делал все на высоте, иногда это выглядело почти правильно, но теперь даже этот трюк, похоже, уже не помогает...


person Ben    schedule 24.06.2015    source источник


Ответы (1)


  1. Вы не должны использовать неинициализированные переменные. Посмотрите на переменную diffuse, вы добавляете значение в неинициализированную переменную. Просто замените:

    highp vec3 diffuse;
    

    с:

    highp vec3 diffuse = vec3(0,0,0);
    

    Если вы используете неинициализированную переменную, поведение может сильно отличаться от драйвера к драйверу.

  2. По возможности избегайте операторов if. Для вашего случая было бы лучше создать три отдельных шейдера для одного, двух и трех источников света. Вы можете использовать препроцессор, чтобы сохранить один исходный файл.
person Podgorskiy    schedule 24.06.2015
comment
Большое вам спасибо, вы только что сэкономили мне часы попыток попробовать что-нибудь :-) Я бы никогда не подумал, что это может быть проблемой (я совсем новичок в GLSL). Почему я должен избегать утверждений «если»? Я хочу быть гибким с источниками света и, возможно, отключать их от одного кадра к другому, не создавая новый шейдер… Может ли это привести к ошибкам? Спасибо еще раз :-) - person Ben; 25.06.2015
comment
@Ben Проблема с ветвлением в шейдерах заключается в том, что они невероятно медленные. Вы можете написать свой собственный препроцессор, который использует конструкцию, аналогичную #ifdef/#endif, для компиляции нескольких шейдеров из одного и того же исходного кода. Будет намного быстрее переключаться между несколькими версиями одного и того же шейдера, чем динамически переходить в самом шейдере. - person JustSid; 01.07.2015
comment
Спасибо большое, постараюсь по возможности избегать "если" и "иначе". К сожалению точное количество огней я знаю только при рисовании, а количество огней передаю как единообразное. Мне пришлось бы изменить это, чтобы вы могли передавать только то количество источников света, которое вы указали при создании шейдера. Это, конечно, менее гибко и приводит к ошибкам. На данный момент я могу передать любое количество источников света, которое захочу, и все это обрабатывается внутри, поэтому их никогда не бывает слишком мало или слишком много. Я подумаю об этом ... - person Ben; 02.07.2015