Lesson 171. OpenGL. Color.
Android Lessons

Lesson 171. OpenGL. Color.


In this lesson:

– convey color for the vertices
– use varying variable

In the last lesson we learned how to draw graphic primitives. Now let’s learn to use different colors.

Let me remind you that we set the color as follows:

glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);

Where uColorLocation is a variable that knows where the u_Color variable responsible for color is located under the fragment shader (see Lesson 169).

The glUniform4f method call can be taken from the bindData method and placed in the onDrawFrame

We are opening a project with a lesson171_colors module in it. We look at the OpenGLRenderer class. It is similar to the same class in the example of the last lesson. That is, it draws the same 4 triangles, 2 lines, and three points. But now he will do it in different colors.

The vertices of all primitives are specified in an array

float[] vertices = {
        // треугольник 1
        -0.9f, 0.8f,
        -0.9f, 0.2f,
        -0.5f, 0.8f,
 
        // треугольник 2
        -0.6f, 0.2f,
        -0.2f, 0.2f,
        -0.2f, 0.8f,
 
        // треугольник 3
        0.1f, 0.8f,
        0.1f, 0.2f,
        0.5f, 0.8f,
 
        // треугольник 4
        0.1f, 0.2f,
        0.5f, 0.2f,
        0.5f, 0.8f,
 
        // линия 1
        -0.7f, -0.1f,
        0.7f, -0.1f,
 
        // линия 2
        -0.6f, -0.2f,
        0.6f, -0.2f,
 
        // точка 1
        -0.5f, -0.3f,
 
        // точка 2
        0.0f, -0.3f,
 
        // точка 3
        0.5f, -0.3f,
};

And in the method onDrawFrame there are changes:

@Override
public void onDrawFrame(GL10 arg0) {
    glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(5);
 
    // синие треугольники
    glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawArrays(GL_TRIANGLES, 0, 12);
 
    // зеленые линии
    glUniform4f(uColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    glDrawArrays(GL_LINES, 12, 4);
 
    // красные точки
    glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    glDrawArrays(GL_POINTS, 16, 3);
}

Before every call glDrawArrays there is a challenge glUniform4fWhich sets the color. That is, the triangles will be blue, the lines will be green and the dots will be red.

run

But what if we wanted to draw one of the lines in yellow, for example. Then we just split the call glDrawArraysWhich draws two lines, on two calls, each of which will draw one line. And before each call we will put the right color.

rewrite onDrawFrame:

public void onDrawFrame(GL10 arg0) {
    glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(5);
 
    // синие треугольники
    glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    glDrawArrays(GL_TRIANGLES, 0, 12);
 
    // зеленая линия
    glUniform4f(uColorLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    glDrawArrays(GL_LINES, 12, 2);
 
    // желтая линия
    glUniform4f(uColorLocation, 1.0f, 1.0f, 0.0f, 1.0f);
    glDrawArrays(GL_LINES, 14, 2);
 
    // красные точки
    glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    glDrawArrays(GL_POINTS, 16, 3);
}

We split one line drawing call into two and set each color to the right color.

Notice the parameters of these two new calls to the glDrawArrays method. The first line uses two vertices, starting with index 12. And the second also has 2 vertices, but starting with index 14. And before division, this method took 4 vertices, starting with index 12.

run

This method of setting the color is quite simple. There is a more interesting way. We can put a color for each vertex of the primitive. And in the process of drawing, the system itself will interpolate the colors of the vertices to the entire surface of the graphic primitive.

That is, for example, we draw a line using two vertices. The first vertex is green and the second vertex is red. The drawn line will be gradient, that is, have a green color on the side of the first vertex, and as it approaches the second vertex the green color will change red. That is, the system itself calculates the colors of all the intermediate pixels between the vertices (this is called the clever word Interpolation).

Let’s rewrite shaders, first riding vertex_shader.glsl:

attribute vec4 a_Position;
attribute vec4 a_Color;
 
varying vec4 v_Color;
 
void main()
{
    gl_Position = a_Position;
    gl_PointSize = 5.0;
 
    v_Color = a_Color;
}

We have added the a_Color attribute. We will pass color values ​​to each vertex, just as we pass the vertex coordinates to a_Position.

Also, we added the v_Color variable. Note the word varying. We already know that there are attribute variables in which we pass separate data for each vertex into vertex shaders. There are uniform variables in which we pass a single value for all vertices in vertex shaders and all points in a fragment shader. Now we get to the third (and last) type of variables in shaders – varying. Such variables are used to exchange data between the vertex and the fragment shader. We vary the variableing ourselves into vertex shaders, then the system interpolates these values ​​and returns the result to us in the Fragment shader.

In our example in vertex shaders we put a_Color in v_Color. That is, if you look at the example of the green-red line, the first vertex will place in v_Color the value of green, and the second – red. The Fragment shader is then executed for each point between these vertices, and in this shader we will get the interpolated value of v_Color. It will be green for the points near the first vertex and gradually change to red as the points redefine the path to the second vertex. This will allow us to draw a green-red line.

All calculations of the values ​​of varying variables are performed by the system. All we need to do is set the values ​​in the vertex shaders and count them as fragmentary.

Let’s rewrite the Fragment Shader fragment_shader.glsl:

precision mediump float;
 
varying vec4 v_Color;
 
void main()
{
    gl_FragColor = v_Color;
}

we add varying v_Color variable. The value in it is already calculated by the system based on data from the vertex shader. All we have to do is write it in gl_FragColor.

Now you need to change the application code to transmit not only the coordinates of the vertices, but also the color in the shader.

change OpenGLRenderer.java:

delete the variable

private int uColorLocation;

and add it instead

private int aColorLocation;

IN prepareData set the vertices:

float[] vertices = {
        // линия 1
        -0.4f, 0.6f, 1.0f, 0.0f, 0.0f,
        0.4f, 0.6f, 0.0f, 1.0f, 0.0f,
 
        // линия 2
        0.6f, 0.4f, 0.0f, 0.0f, 1.0f,
        0.6f, -0.4f, 1.0f, 1.0f, 1.0f,
 
        // линия 3
        0.4f, -0.6f, 1.0f, 1.0f, 0.0f,
        -0.4f, -0.6f, 1.0f, 0.0f, 1.0f,
};

We will draw three lines, ie we set 6 vertices. But now for each vertex there are not only two XY coordinates, but also three RGB color components. Total 2 + 3 = 5 values ​​for each vertex.

Let’s rewrite bindData:

private void bindData() {
    // координаты
    aPositionLocation = glGetAttribLocation(programId, "a_Position");
    vertexData.position(0);
    glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 20, vertexData);
    glEnableVertexAttribArray(aPositionLocation);
 
    // цвет
    aColorLocation = glGetAttribLocation(programId, "a_Color");
    vertexData.position(2);
    glVertexAttribPointer(aColorLocation, 3, GL_FLOAT, false, 20, vertexData);
    glEnableVertexAttribArray(aColorLocation);
}

First, we transmit data by coordinates. Here, almost unchanged, only in method glVertexAttribPointer the fifth parameter we pass 20. Previously we passed here 0.

This fifth parameter is called stride. It must place the number of bytes that are occupied by our array of data on each vertex. We have 5 float values ​​for each vertex: 2 coordinates (XY) and three color components (RGB). 5 float values ​​are 5 * 4 bytes = 20 bytes. This is the value we convey in stride.

That is, if we look at these two lines

vertexData.position(0);
glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 20, vertexData);

then the following scheme will be obtained:

1) the position in the vertexData array is set to 0, that is, to the first element
2) the system takes 2 float values ​​(ie vertex coordinates) from vertexData and passes them to aPositionLocation (corresponding to the a_Position attribute in vertex shaders)
3) the position is moved by 20 bytes, that is, to the coordinates of the next vertex.

Items 2 and 3 are executed as many times as the vertices need to be drawn. An offset of 20 bytes each time will set the position in the array to the coordinates of the next vertex.

Let’s look further. Everything is similar here. At aColorLocation, we get the location of the a_Color attribute and execute the code

vertexData.position(2);
glVertexAttribPointer(aColorLocation, 3, GL_FLOAT, false, 20, vertexData);

1) the position in the vertexData array is set to 2, that is, to the third element (where the first vertex color data begins)
2) the system takes 3 float values ​​(ie the RGB components of the vertex color) from vertexData and passes them to aColorLocation (which corresponds to the a_Color attribute in vertex shaders)
3) the position is moved by 20 bytes, that is, to the color of the next vertex

Items 2 and 3 are executed as many times as the vertices need to be drawn. A 20 byte offset each time will set the array position to the next vertex color data.

Remaining to overwrite onDrawFrame method:

@Override
public void onDrawFrame(GL10 arg0) {
    glLineWidth(5);
    glDrawArrays(GL_LINES, 0, 6);
}

As you can see, here we just ask the system to draw us lines using 6 vertices. And we say nothing about color or coordinates. The system will run the riding shader 6 times, and thanks to the glVertexAttribPointer methods (which we just discussed in detail) will be able to figure out what data from the array it will need to use as the coordinates of the vertices (it will pass them in a_Position), and which – as color data (a_Color).

run the program

As a result, we see lines whose color changes from one vertex to another. This is a result of the fact that we passed the color data to the riding shader and used varying variables.

Let’s rewrite onDrawFrame:

@Override
public void onDrawFrame(GL10 arg0) {
    glLineWidth(5);
    glDrawArrays(GL_LINE_LOOP, 0, 6);
}

We use a line drawing mode that connects all vertices with each other.

run

Finally, we draw a triangle with vertices of different colors and see how it interpolates these colors to its entire surface.

Rewrite the vertices array to prepareData:

float[] vertices = {
        -0.5f, -0.2f, 1.0f, 0.0f, 0.0f,
        0.0f, 0.2f, 0.0f, 1.0f, 0.0f,
        0.5f, -0.2f, 0.0f, 0.0f, 1.0f,
};

We set three vertices, each of which is filled with two coordinates and three color components.

In the onDrawFrame method, we ask you to draw a triangle.

@Override
public void onDrawFrame(GL10 arg0) {
    glLineWidth(5);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

run

and get a gradient fill

I hope that after this lesson, a general picture of the shader mechanism began to emerge. It can be divided into points:

1) The method glDrawArrays, In which we specify what figures to draw and how many vertices to use. How many vertices we will indicate here, so many times the horse shader will sound.

2) The riding shader has the attributes in which we need to transmit vertices. The method is responsible for this glVertexAttribPointer, In which we explain in detail the system from which array to retrieve data and by what rules (access, data type, number of values ​​per vertex)

3) Running riding shaderIn which we are still simply passing the received data further to the drawing – gl_Position. At these coordinates, the system will draw the vertices of the primitives. Also in vertex shader we use varying variable to interpolate color and pass it to Fragment shader.

4)Fragment shader is used to draw the contents of a primitive. That is, it is called for each point of the primitive. In our case, it gets the interpolated color and passes it further into gl_FragColor. We will see this color on the screen.

I recommend playing around with an array of vertices and trying to put your coordinates and colors there, and use different shapes in the glDrawArrays method. This will help you better understand all these mechanisms.




Discuss in the forum [6 replies]

Leave a Reply

Your email address will not be published. Required fields are marked *