В этом уроке:

- рисуем графические примитивы

 

Исходники уроков доступны на гитхабе. Скачивайте проект, в нем будем использовать модуль lesson170_primitives.

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

 

Треугольник

Сейчас наше приложение рисует один треугольник. Если мы посмотрим на класс OpenGLRenderer, то в методе prepareData увидим в нем список вершин:

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

Каждая пара значений – это координаты (x,y) одной вершины. Три пары = три вершины = треугольник.

 

Далее, в методе onDrawFrame мы используем метод glDrawArrays чтобы нарисовать треугольник.

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

GL_TRIANGLES - тип примитива, который необходимо нарисовать, треугольник в нашем случае

0 - вершины надо брать из массива начиная с позиции 0, т.е. с самой первой

3 - означает, что для рисования необходимо использовать три вершины

 

Запускаем приложение

 

 

Теперь попробуем нарисовать 4 треугольника. Для этого нам понадобится больше вершин. 4 треугольника, в каждом по три вершины, значит нам нужно 3*4=12 вершин.

Перепишем массив vertices в методе prepareData

        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,
        };

Теперь у нас есть 12 вершин из которых можно построить 4 треугольника.

Запускаем

Но видим только один треугольник вместо 4-х. Мы забыли сказать системе, что надо рисовать треугольники используя 12 вершин. Т.е. в метод glDrawArrays мы до сих пор передаем значение 3, а это значит что система возьмет из массива значения, чтобы нарисовать только три вершины.

Перепишем onDrawFrame

    @Override public void onDrawFrame(GL10 arg0) {
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_TRIANGLES, 0, 12);
    }

В методе glDrawArrays укажем 12 вместо 3. Теперь система будет знать, что из массива ей необходимо использовать данные, чтобы сформировать 12 вершин для рисования треугольников, и мы получим 4 треугольника.

Запускаем

Видим два треугольника и прямоугольник. Этот прямоугольник на самом деле состоит из двух треугольников, расположенных впритык друг к другу.

Посмотрите на массив вершин и обратите внимание, что у треугольников 3 и 4 есть общие вершины (0.1f, 0.2f) и (0.5f, 0.8f). Вот по этой стороне они и соединились образовав прямоугольник.

В итоге мы получили 4 треугольника, два из которых выглядят, как один прямоугольник.

 

Типы треугольников

Чтобы нарисовать треугольник, мы передаем тип GL_TRIANGLES в метод glDrawArrays. Существует еще два типа отрисовки треугольников: GL_TRIANGLE_STRIP и GL_TRIANGLE_FAN.

В чем разница между ними? Смотрим рисунок

 

GL_TRIANGLES – каждые три переданные вершины образуют треугольник. Т.е.

v0, v1, v2 – первый треугольник

v3, v4, v5 – второй треугольник

 

GL_TRIANGLE_STRIP – каждый следующий треугольник использует две последние вершины предыдущего

v0, v1, v2 – первый треугольник

v1, v2, v3 – второй треугольник

v2, v3, v4 – третий треугольник

v3, v4, v5 – четвертый треугольник

 

GL_TRIANGLE_FAN – каждый следующий треугольник использует последнюю вершину предыдущего и самую первую вершину

v0, v1, v2 – первый треугольник

v0, v2, v3 – второй треугольник

v0, v3, v4 – третий треугольник

 

Рассмотрим эти типы на примерах

Задаем 6 вершин в prepareData:

float[] vertices = { 0.1f, 0.8f, 0.1f, 0.2f, 0.5f, 0.8f, 0.1f, 0.2f, 0.5f, 0.2f, 0.5f, 0.8f, };

 

тип GL_TRIANGLES и 6 вершин в glDrawArrays:

glDrawArrays(GL_TRIANGLES, 0, 6);

Запускаем

Получаем прямоугольник, составленный из двух треугольников.

 

Теперь нарисуем тот же прямоугольник, но чуть по-другому

Задаем 4 вершины

float[] vertices = { 0.1f, 0.8f, 0.1f, 0.2f, 0.5f, 0.8f, 0.5f, 0.2f, };

тип GL_TRIANGLE_STRIP и 4 вершины

glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

 

Если у вас среда разработки ругается на константу GL_TRIANGLE_STRIP, то добавьте ее в импорт в начале класса:

import static android.opengl.GLES20.GL_TRIANGLE_STRIP;

И для всех последующих констант делайте аналогично.

Запускаем

Результат тот же, но в этот раз мы использовали 4 вершины, а не 6. Тип треугольника GL_TRIANGLE_STRIP помог немного сэкономить. В данном примере это конечно не особо критично, но, в целом, чем меньше вершин нам приходится передавать, тем выше скорость работы приложения.

 

 

Рассмотрим последний тип. Задаем 8 вершин

        float[] vertices = { 
                0.0f, 0.0f, 
                -0.4f, 0.4f, 
                0.4f, 0.4f, 
                0.8f, 0.0f, 
                0.4f, -0.4f, 
                -0.4f, -0.4f, 
                -0.8f, 0.0f, 
                -0.4f, 0.4f, 
        };

 

тип GL_TRIANGLE_FAN и 8 вершин

glDrawArrays(GL_TRIANGLE_FAN, 0, 8);

Запускаем

Получили шестиугольник. Для этого мы указали центральную вершину и вершины-углы, и в режиме GL_TRIANGLE_FAN система нарисовала шестиугольник.

 

Линия

Переходим к линии. Чтобы нарисовать линию нам необходимо указать две вершины.

Задаем их в массиве:

float[] vertices = { -0.9f, -0.9f, 0.9f, 0.9f, };

 

И перепишем onDrawFrame:

    @Override 
    public void onDrawFrame(GL10 arg0) { 
        glClear(GL_COLOR_BUFFER_BIT); 
        glDrawArrays(GL_LINES, 0, 2); 
    }

 

Используем константу GL_LINES и указываем, что надо использовать две вершины.

Запускаем

 

С одной линией все понятно, попробуем нарисовать три линии.

Указываем вершины

        float[] vertices = { 
                // линия 1 
                -0.9f, -0.9f, 0.9f, 0.9f, 
                
                // линия 2 
                -0.5f, 0.0f, 0.5f, 0.0f, 
                
                // линия 3 
                0.0f, 0.7f, 0.0f, -0.7f, 
        };

 

Не забываем указать в glDrawArrays, что необходимо использовать 6 вершин, и зададим толщину линии = 5.

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

Запускаем

Нарисованы три линии

 

 

Типы линий

Существует три типа отрисовки линий. Разберем их на примерах.

Тип GL_LINES мы уже использовали, он просто берет попарно вершины и рисует линии между ними. Т.е. если у нас есть вершины (v0, v1, v2, v3, v4, v5), то мы получим три линии (v0,v1), (v2,v3) и (v4,v5).

 

Задаем вершины

        float[] vertices = {
                -0.4f, 0.6f, 
                0.4f, 0.6f, 
                0.6f, 0.4f,
                0.6f, -0.4f, 
                0.4f, -0.6f, 
                -0.4f, -0.6f,
        };

 

Указываем тип GL_LINES, 6 вершин

glDrawArrays(GL_LINES, 0, 6);

Запускаем

Каждая пара вершин образовала линию.

 

Тип GL_LINE_STRIP рисует линии не попарно, а последовательно между всеми вершинами. Т.е. если у нас есть вершины (v0, v1, v2, v3, v4, v5), то мы получим пять линий (v0,v1), (v1,v2), (v2,v3), (v3,v4) и (v4,v5).

Вершины мы будем использовать те же, а тип поменяем на GL_LINE_STRIP

glDrawArrays(GL_LINE_STRIP, 0, 6); 

Запускаем

Линии нарисованы последовательно между всеми вершинами

 

Тип GL_LINE_LOOP аналогичен GL_LINE_STRIP, только он вдобавок еще рисует линию между первой и последней точкой.

Меняем тип на GL_LINE_LOOP

glDrawArrays(GL_LINE_LOOP, 0, 6); 

Запускаем

Результат тот же, что и при GL_LINE_STRIP, плюс есть линия между первой и последней вершинами.

 

Точка

Осталось рассмотреть точку. Здесь уже нет никаких разных типов, только GL_POINTS.

Укажем его в glDrawArrays:

    @Override 
    public void onDrawFrame(GL10 arg0) { 
        glClear(GL_COLOR_BUFFER_BIT); 
        glDrawArrays(GL_POINTS, 0, 6); 
    }

Вершины оставим те же.

Толщину точки можно задать в вершинном шейдере, используя переменную gl_PointSize.

vertex_shader.glsl

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

Запускаем

Нарисованы 6 точек

 

Ну и напоследок давайте нарисуем сразу несколько разных примитивов, например: 4 треугольника, 2 линии, 3 точки

4 треугольника – это 4 * 3 = 12 вершин

2 линии – это 2 * 2 = 4 вершины

3 точки – это 3 вершины

Итого нам нужно задать 12 + 4 + 3 = 19 вершин

 

        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, 
        }; 

 

Перепишем onDrawFrame

    @Override 
    public void onDrawFrame(GL10 arg0) { 
        glClear(GL_COLOR_BUFFER_BIT); 
        glLineWidth(5); 
        glDrawArrays(GL_TRIANGLES, 0, 12); 
        glDrawArrays(GL_LINES, 12, 4); 
        glDrawArrays(GL_POINTS, 16, 3); 
    }

 

Мы три раза вызываем метод glDrawArrays.

Первый вызов говорит системе о том, что надо нарисовать треугольники и использовать при этом 12 вершин, начиная с первой в массиве (индекс 0).

Второй вызов говорит системе о том, что надо нарисовать линии и использовать при этом 4 вершины, начиная с тринадцатой в массиве (индекс 12). Начинаем с тринадцатой потому, что первые 12 вершин в массиве мы использовали, чтобы задать треугольники, а вершины линий у нас идут начиная с тринадцатой.

Третий вызов говорит системе о том, что надо нарисовать точки и использовать при этом 3 вершины, начиная с семнадцатой в массиве (индекс 16). Начинаем с семнадцатой потому, что первые 12 вершин в массиве мы использовали, чтобы задать треугольники, следующие 4 вершины мы использовали, чтобы задать линии, а вершины точек у нас идут начиная с семнадцатой.

Запускаем

Система нарисовала 4 треугольника (2 их из них образуют прямоугольник), 2 линии и три точки.

 

Надеюсь, стала понятнее связь между массивом вершин и методом glDrawArrays. Т.е. в glDrawArrays мы указываем какие фигуры рисовать и сколько вершин для этого использовать, а данные по вершинам система берет из массива vertices. Возможно осталось непонятным, например, как система определяет, что для вершины надо брать именно 2 точки из массива vertices. Это мы подробно разберем на следующем уроке и алгоритм передачи данных в шейдеры станет полностью понятным. 

 


Присоединяйтесь к нам в Telegram:

- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance 

- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня




Language

Автор сайта

Дмитрий Виноградов

Подробнее можно посмотреть или почитать.

Никакие другие люди не имеют к этому сайту никакого отношения и просто занимаются плагиатом.

Социальные сети

 

В канале я публикую ссылки на интересные и полезные статьи по Android

В чате можно обсудить вопросы и проблемы, возникающие при разработке



Группа ВКонтакте



Поддержка проекта

Яндекс
410011180491924

WebMoney
R248743991365
Z551306702056

Paypal