OpenGL ES glFragColor зависит от того, если условие на шейдер фрагмента в iOS

Я пишу приложение для iOS, которое позволяет рисовать в свободном стиле (используя палец) и рисовать изображение на экране. Я использую OpenGL ES для реализации. У меня есть 2 функции, одна рисует свободный стиль, одна рисует текстуру

--- код рисования свободного стиля

- (void)drawFreeStyle:(NSMutableArray *)pointArray {

        //Prepare vertex data
        .....

        // Load data to the Vertex Buffer Object
        glBindBuffer(GL_ARRAY_BUFFER, vboId);
        glBufferData(GL_ARRAY_BUFFER, vertexCount*2*sizeof(GLfloat), vertexBuffer, GL_DYNAMIC_DRAW);

        glEnableVertexAttribArray(ATTRIB_VERTEX);
        glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 0, 0);

        **GLuint a_ver_flag_drawing_type = glGetAttribLocation(program[PROGRAM_POINT].id, "a_drawingType");
        glVertexAttrib1f(a_ver_flag_drawing_type, 0.0f);

        GLuint u_fra_flag_drawing_type = glGetUniformLocation(program[PROGRAM_POINT].id, "v_drawing_type");
        glUniform1f(u_fra_flag_drawing_type, 0.0);**

        glUseProgram(program[PROGRAM_POINT].id);
        glDrawArrays(GL_POINTS, 0, (int)vertexCount);

        // Display the buffer
        glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
        [context presentRenderbuffer:GL_RENDERBUFFER];
}

--- Код рисования текстуры

- (void)drawTexture:(UIImage *)image atRect:(CGRect)rect {

    GLuint a_ver_flag_drawing_type = glGetAttribLocation(program[PROGRAM_POINT].id, "a_drawingType");

    GLuint u_fra_flag_drawing_type = glGetUniformLocation(program[PROGRAM_POINT].id, "v_drawing_type");

    GLuint a_position_location = glGetAttribLocation(program[PROGRAM_POINT].id, "a_Position");
    GLuint a_texture_coordinates_location = glGetAttribLocation(program[PROGRAM_POINT].id, "a_TextureCoordinates");
    GLuint u_texture_unit_location = glGetUniformLocation(program[PROGRAM_POINT].id, "u_TextureUnit");

    glUseProgram(PROGRAM_POINT);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texName);
    glUniform1i(u_texture_unit_location, 0);
    glUniform1f(u_fra_flag_drawing_type, 1.0);

    const float textrect[] = {-1.0f, -1.0f, 0.0f, 0.0f,
        -1.0f,  1.0f, 0.0f, 1.0f,
        1.0f, -1.0f, 1.0f, 0.0f,
        1.0f,  1.0f, 1.0f, 1.0f};

    glBindBuffer(GL_ARRAY_BUFFER, vboId);
    glBufferData(GL_ARRAY_BUFFER, sizeof(textrect), textrect, GL_STATIC_DRAW);

    glVertexAttrib1f(a_ver_flag_drawing_type, 1.0f);

    glVertexAttribPointer(a_position_location, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(0));
    glVertexAttribPointer(a_texture_coordinates_location, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));

    glEnableVertexAttribArray(a_ver_flag_drawing_type);
    glEnableVertexAttribArray(a_position_location);
    glEnableVertexAttribArray(a_texture_coordinates_location);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];
}

Обратите внимание на 2 переменные a_ver_flag_drawing_type (атрибут) и u_fra_flag_drawing_type (Форма). Они используются для установки флагов на вершинный шейдер и фрагментный шейдер, чтобы определить свободный стиль рисования или текстуру для обоих файлов.

--- вершинный шейдер

//Flag
attribute lowp float a_drawingType;

//For drawing
attribute vec4 inVertex;

uniform mat4 MVP;
uniform float pointSize;
uniform lowp vec4 vertexColor;

varying lowp vec4 color;

//For texture
attribute vec4 a_Position;
attribute vec2 a_TextureCoordinates;

varying vec2 v_TextureCoordinates;

void main()
{
    if (abs(a_drawingType - 1.0) < 0.0001) {
        //Draw texture        
        v_TextureCoordinates = a_TextureCoordinates;
        gl_Position = a_Position;
    } else {
        //Draw free style
        gl_Position = MVP * inVertex;
        gl_PointSize = pointSize;
        color = vertexColor;
    }

}

--- Фрагмент шейдера

precision mediump float;

uniform sampler2D texture;
varying lowp vec4 color;

uniform sampler2D u_TextureUnit;
varying vec2 v_TextureCoordinates;

uniform lowp float v_drawing_type;

void main()
{
    if (abs(v_drawing_type - 1.0) < 0.0001) {
        //Draw texture
        gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
    } else {
        //Drawing free style
        gl_FragColor = color * texture2D(texture, gl_PointCoord);
    }

}

Моя идея заключается в установке этих флагов из кода рисования во время рисования. атрибут a_drawingType используется для вершинного шейдера и униформы v_drawing_type используется для фрагмента шейдера. В зависимости от этих флагов нужно нарисовать свободный стиль или текстуру.

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

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

Я новичок в OpenGL ES и GLSL, так что я не уверен, правильно ли я думаю о настройке флагов. Может кто-нибудь мне помочь

4 ответа

Так почему бы вам просто не построить две отдельные программы шейдеров и не использовать Program() для одной из них, вместо того, чтобы отправлять значения флагов в GL и создавать дорогостоящие условные переходы в вершинах и особенно фрагментные шейдеры?

Ранее опубликованные ответы объясняют определенные части, но важная часть отсутствует во всех из них. Вполне допустимо указывать одно значение атрибута, которое применяется ко всем вершинам в вызове отрисовки. То, что вы сделали здесь, было в основном верно:

glVertexAttrib1f(a_ver_flag_drawing_type, 1.0f);

Непосредственной проблемой был этот звонок, который последовал вскоре после:

glEnableVertexAttribArray(a_ver_flag_drawing_type);

Есть два основных способа указать значение атрибута вершины:

  1. Используйте текущее значение, как указано в вашем случае glVertexAttrib1f(),
  2. Используйте значения из массива, как указано в glVertexAttribPointer(),

Вы выбираете, какой из двух вариантов используется для любого данного атрибута, путем включения / выключения массива, что делается путем вызова glEnableVertexAttribArray() / glDisableVertexAttribArray(),

В опубликованном коде атрибут вершины был указан только как текущее значение, но затем атрибуту было разрешено извлекать данные из массива с glEnableVertexAttribArray(), Этот конфликт вызвал сбой, поскольку значения атрибутов были бы получены из массива, который никогда не указывался. Чтобы использовать указанное текущее значение, вызов просто должен быть изменен на:

glDisableVertexAttribArray(a_ver_flag_drawing_type);

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

Как примечание, следующая последовательность операторов из первой функции отрисовки также выглядит подозрительно. glUniform*() устанавливает значение для активной программы, поэтому будет установлено значение для ранее активной программы, а не значение, указанное во втором операторе. Если вы хотите установить значение для новой программы, порядок операторов должен быть обратным.

glUniform1f(u_fra_flag_drawing_type, 0.0);
glUseProgram(program[PROGRAM_POINT].id);

В целом, я думаю, что есть по крайней мере два подхода, которые лучше, чем выбранный:

  1. Используйте отдельные шейдерные программы для двух разных типов рендеринга. Хотя использование одной программы с переключаемым поведением является допустимой опцией, она выглядит искусственно, а использование отдельных программ выглядит намного чище.
  2. Если вы хотите придерживаться одной программы, используйте одну униформу для переключения вместо использования атрибута и униформы. Вы могли бы использовать тот, который у вас уже есть, но вы также можете сделать его логическим, пока вы на нем. Таким образом, в вершинном и фрагментном шейдерах используйте одно и то же унифицированное объявление:

    uniform bool v_use_texture;
    

    Тогда испытания становятся:

    if (v_use_texture) {
    

    Получение унифицированного местоположения такое же, как и раньше, и вы можете установить значение, которое будет доступно как в вершинном, так и фрагментном шейдерах, с одним из:

    glUniform1i(loc, 0);
    glUniform1i(loc, 1);
    

Атрибуты для каждой вершины, униформы для каждой программы.

Вы можете увидеть сбой, если вы предоставите только одно значение для атрибута, а затем попросите OpenGL нарисовать, скажем, 100 точек. В этом случае OpenGL будет делать доступ к массиву вне пределов, когда он пытается извлечь атрибуты для вершин 2–100.

Было бы более нормально использовать две отдельные программы. Условные вычисления очень дороги для графических процессоров, потому что они пытаются поддерживать один счетчик программ при обработке нескольких фрагментов. Они SIMD единицы. В любое время оценка if отличается между двумя соседними фрагментами, вы, вероятно, уменьшаете параллелизм. Поэтому идиома не использовать if заявления, где это возможно.

Если вы переключитесь на uniform есть большая вероятность, что вы не потеряете производительность из-за отсутствия параллелизма, потому что пути никогда не расходятся. Ваш GLSL-компилятор может даже быть достаточно умным, чтобы перекомпилировать шейдер каждый раз, когда вы сбрасываете константу, эффективно выполняя константное свертывание. Но вы будете платить за перекомпиляцию каждый раз.

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

Это не совсем актуально в этом случае, но, например, вы также часто видите код вроде:

if (abs(v_drawing_type - 1.0) < 0.0001) {
    //Draw texture
    gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
} else {
    //Drawing free style
    gl_FragColor = color * texture2D(texture, gl_PointCoord);
}

Написано больше как:

gl_FragColor = mix(texture2D(u_TextureUnit, v_TextureCoordinates),
                   color * texture2D(texture, gl_PointCoord),
                   v_drawing_type);

... потому что это полностью избегает условного. В этом случае вы хотели бы настроить texture2D так, чтобы оба вызова были идентичными, и, вероятно, исключали их из вызова, чтобы вы не всегда делали два образца вместо одного.

Я обнаружил проблему, просто изменив переменную a_drawingType с Attribute на Uniform, затем используйте glGetUniformLocation и glUniform1f, чтобы получить индекс и передать значение. Я думаю, что Атрибут пройдет для любой вершины, поэтому используйте форму, чтобы пройти один раз.

Другие вопросы по тегам