OpenGL ES glFragColor依赖于iOS中Fragment着色器的条件

时间:2014-12-16 01:32:04

标签: ios opengl-es fragment-shader vertex-shader

我在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 个答案:

答案 0 :(得分:2)

那么为什么不在其中一个上构建2个单独的着色器程序和useProgram(),而不是向GL发送标志值并在顶点中制作昂贵的条件分支,尤其是片段着色器?

答案 1 :(得分:1)

属性是每个顶点,制服是每个着色器程序。

如果您只为某个属性提供了一个值,那么您可能会看到崩溃,然后要求OpenGL绘制100个点。在这种情况下,当OpenGL尝试获取顶点2-100的属性时,它将进行越界数组访问。

使用两个单独的程序更为正常。 GPU上的条件非常昂贵,因为GPU在处理多个片段时尝试维护一个程序计数器。他们是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,以便两个来电都相同,并可能将其排除在通话之外,以确保您不会总是做两个样本而不是一个

答案 2 :(得分:1)

之前发布的答案解释了某些部分,但所有部分都缺少一个重要部分。指定应用于绘制调用中所有顶点的单个属性值是完全合法的。你在这里做的基本上是有效的:

glVertexAttrib1f(a_ver_flag_drawing_type, 1.0f);

直接的问题是紧随其后的这个电话:

glEnableVertexAttribArray(a_ver_flag_drawing_type);

有两种主要方法可以指定顶点属性的值:

  1. 使用glVertexAttrib1f()
  2. 中指定的当前值
  3. 使用glVertexAttribPointer()指定的数组值。
  4. 通过启用/禁用数组,您可以选择将两个选项中的哪一个用于任何给定属性,方法是调用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);
      

答案 3 :(得分:0)

我发现了问题,只需将变量a_drawingType从Attribute更改为Uniform,然后使用glGetUniformLocation和glUniform1f获取索引并传递值。我认为属性将传递给任何顶点,所以使用uniform来传递一次。