Рисовать фигуры

После того как вы определите фигуры, которые будут рисоваться с помощью OpenGL, вы, вероятно, захотите их нарисовать. Для рисования фигур с помощью OpenGL ES 2.0 требуется немного больше кода, чем вы можете себе представить, поскольку API обеспечивает широкие возможности контроля над конвейером рендеринга графики.

В этом уроке объясняется, как рисовать фигуры, определенные на предыдущем уроке, с помощью API OpenGL ES 2.0.

Инициализация фигур

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

Котлин

class MyGLRenderer : GLSurfaceView.Renderer {
    ...
    private lateinit var mTriangle: Triangle
    private lateinit var mSquare: Square

    override fun onSurfaceCreated(unused: GL10, config: EGLConfig) {
        ...
        // initialize a triangle
        mTriangle = Triangle()
        // initialize a square
        mSquare = Square()
    }
    ...
}

Ява

public class MyGLRenderer implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;
    private Square   mSquare;

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        ...
        // initialize a triangle
        mTriangle = new Triangle();
        // initialize a square
        mSquare = new Square();
    }
    ...
}

Нарисуйте фигуру

Для рисования определенной формы с помощью OpenGL ES 2.0 требуется значительный объем кода, поскольку необходимо предоставить много деталей конвейеру рендеринга графики. В частности, вы должны определить следующее:

  • Vertex Shader — графический код OpenGL ES для рендеринга вершин фигуры.
  • Фрагментный шейдер — код OpenGL ES для рендеринга грани фигуры с помощью цветов или текстур.
  • Программа — объект OpenGL ES, содержащий шейдеры, которые вы хотите использовать для рисования одной или нескольких фигур.

Вам понадобится как минимум один вершинный шейдер, чтобы нарисовать фигуру, и один фрагментный шейдер, чтобы раскрасить эту фигуру. Эти шейдеры необходимо скомпилировать, а затем добавить в программу OpenGL ES, которая затем используется для рисования фигуры. Вот пример того, как определить базовые шейдеры, которые можно использовать для рисования фигуры в классе Triangle :

Котлин

class Triangle {

    private val vertexShaderCode =
            "attribute vec4 vPosition;" +
            "void main() {" +
            "  gl_Position = vPosition;" +
            "}"

    private val fragmentShaderCode =
            "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            "  gl_FragColor = vColor;" +
            "}"

    ...
}

Ява

public class Triangle {

    private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
        "void main() {" +
        "  gl_Position = vPosition;" +
        "}";

    private final String fragmentShaderCode =
        "precision mediump float;" +
        "uniform vec4 vColor;" +
        "void main() {" +
        "  gl_FragColor = vColor;" +
        "}";

    ...
}

Шейдеры содержат код языка шейдинга OpenGL (GLSL), который необходимо скомпилировать перед его использованием в среде OpenGL ES. Чтобы скомпилировать этот код, создайте служебный метод в классе вашего рендерера:

Котлин

fun loadShader(type: Int, shaderCode: String): Int {

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    return GLES20.glCreateShader(type).also { shader ->

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode)
        GLES20.glCompileShader(shader)
    }
}

Ява

public static int loadShader(int type, String shaderCode){

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // add the source code to the shader and compile it
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}

Чтобы нарисовать фигуру, вы должны скомпилировать код шейдера, добавить его в программный объект OpenGL ES, а затем связать программу. Сделайте это в конструкторе нарисованного объекта, чтобы это делалось только один раз.

Примечание. Компиляция шейдеров OpenGL ES и связывание программ требует больших затрат ресурсов ЦП и времени обработки, поэтому не следует делать это более одного раза. Если вы не знаете содержимое своих шейдеров во время выполнения, вам следует построить свой код так, чтобы они создавались только один раз, а затем кэшировались для дальнейшего использования.

Котлин

class Triangle {
    ...

    private var mProgram: Int

    init {
        ...

        val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram().also {

            // add the vertex shader to program
            GLES20.glAttachShader(it, vertexShader)

            // add the fragment shader to program
            GLES20.glAttachShader(it, fragmentShader)

            // creates OpenGL ES program executables
            GLES20.glLinkProgram(it)
        }
    }
}

Ява

public class Triangle() {
    ...

    private final int mProgram;

    public Triangle() {
        ...

        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                                        vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                                        fragmentShaderCode);

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }
}

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

Создайте метод draw() для рисования фигуры. Этот код устанавливает значения позиции и цвета для вершинного и фрагментного шейдера фигуры, а затем выполняет функцию рисования.

Котлин

private var positionHandle: Int = 0
private var mColorHandle: Int = 0

private val vertexCount: Int = triangleCoords.size / COORDS_PER_VERTEX
private val vertexStride: Int = COORDS_PER_VERTEX * 4 // 4 bytes per vertex

fun draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram)

    // get handle to vertex shader's vPosition member
    positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition").also {

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(it)

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(
                it,
                COORDS_PER_VERTEX,
                GLES20.GL_FLOAT,
                false,
                vertexStride,
                vertexBuffer
        )

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor").also { colorHandle ->

            // Set color for drawing the triangle
            GLES20.glUniform4fv(colorHandle, 1, color, 0)
        }

        // Draw the triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(it)
    }
}

Ява

private int positionHandle;
private int colorHandle;

private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

public void draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // get handle to vertex shader's vPosition member
    positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(positionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // get handle to fragment shader's vColor member
    colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // Set color for drawing the triangle
    GLES20.glUniform4fv(colorHandle, 1, color, 0);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(positionHandle);
}

Когда у вас есть весь этот код, для рисования этого объекта потребуется просто вызвать метод draw() из метода onDrawFrame() вашего средства визуализации:

Котлин

override fun onDrawFrame(unused: GL10) {
    ...

    mTriangle.draw()
}

Ява

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

Когда вы запустите приложение, оно должно выглядеть примерно так:

Рисунок 1. Треугольник, нарисованный без проекции и вида камеры.

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

Наконец, треугольник неподвижен, что немного скучно. На уроке «Добавление движения» вы заставите эту фигуру вращаться и более интересно воспользуетесь графическим конвейером OpenGL ES.