Урок 4: Цветной куб
Добро пожаловать на наш четвертый урок! Сегодня мы займемся:
- Рисованием куба, вместо скучного треугольника
- Добавлением цвета
- Изучением Буфера Глубины (Z-Buffer)
Рисование куба
Куб имеет 6 прямоугольных граней, однако OpenGL знает только о треугольниках, поэтому все, что мы делаем - это выводим 12 треугольников (по 2 на каждую грань). Задаем вершины точно также, как мы делали это для треугольника:
1 // Наши вершины. Три вещественных числа дают нам вершину. Три вершины дают нам треугольник.
2 // Куб имеет 6 граней или 12 треугольников, значит нам необходимо 12 * 3 = 36 вершин для описания куба.
3 static const GLfloat g_vertex_buffer_data[] = {
4 -1.0f,-1.0f,-1.0f, // Треугольник 1 : начало
5 -1.0f,-1.0f, 1.0f,
6 -1.0f, 1.0f, 1.0f, // Треугольник 1 : конец
7 1.0f, 1.0f,-1.0f, // Треугольник 2 : начало
8 -1.0f,-1.0f,-1.0f,
9 -1.0f, 1.0f,-1.0f, // Треугольник 2 : конец
10 1.0f,-1.0f, 1.0f,
11 -1.0f,-1.0f,-1.0f,
12 1.0f,-1.0f,-1.0f,
13 1.0f, 1.0f,-1.0f,
14 1.0f,-1.0f,-1.0f,
15 -1.0f,-1.0f,-1.0f,
16 -1.0f,-1.0f,-1.0f,
17 -1.0f, 1.0f, 1.0f,
18 -1.0f, 1.0f,-1.0f,
19 1.0f,-1.0f, 1.0f,
20 -1.0f,-1.0f, 1.0f,
21 -1.0f,-1.0f,-1.0f,
22 -1.0f, 1.0f, 1.0f,
23 -1.0f,-1.0f, 1.0f,
24 1.0f,-1.0f, 1.0f,
25 1.0f, 1.0f, 1.0f,
26 1.0f,-1.0f,-1.0f,
27 1.0f, 1.0f,-1.0f,
28 1.0f,-1.0f,-1.0f,
29 1.0f, 1.0f, 1.0f,
30 1.0f,-1.0f, 1.0f,
31 1.0f, 1.0f, 1.0f,
32 1.0f, 1.0f,-1.0f,
33 -1.0f, 1.0f,-1.0f,
34 1.0f, 1.0f, 1.0f,
35 -1.0f, 1.0f,-1.0f,
36 -1.0f, 1.0f, 1.0f,
37 1.0f, 1.0f, 1.0f,
38 -1.0f, 1.0f, 1.0f,
39 1.0f,-1.0f, 1.0f
40 };OpenGL буфер создается, привязывается, заполняется и конфигурируется стандартными функциями (glGenBuffers, glBindBuffer, glBufferData, glVertexAttribPointer); Смотрите Урок 2, чтобы освежить память. Сама процедура вывода не меняется и все, что меняется - это количество вершин, которые мы будем выводить:
1 // Вывести треугольник
2 glDrawArrays(GL_TRIANGLES, 0, 12*3); // 12*3 индексов начинающихся с 0. -> 12 треугольников -> 6 граней.Несколько заметок по этому коду:
- Сейчас наша модель статична, таким образом, чтобы изменить ее, нам понадобится изменить исходных код, перекомпилировать проект и надеяться на лучшее. Мы узнаем как загружать модели во время выполнения программы в Уроке 7.
- Каждая вершина в нашем случае указывается как минимум три раза (например посмотрите на “-1.0f, -1.0f, -1.0f” в коде выше). Это бесполезная растрата памяти и вычислительной мощности. Мы узнаем, как избавиться от дублирующихся вершин в Уроке 9.
Добавление цвета
Цвет в понимании OpenGL - это тоже самое, что и позиция, т. е. просто данные. В терминологии они называются атрибутами. Мы уже работали с ними, с помощью таких функций, как: glEnableVertexAttribArray() и glVertexAttribPointer(). Теперь мы добавим еще один атрибут и код для этого действия будет очень похож.
Первым делом мы объявляем наши цвета - один RGB триплет на вершину. Этот массив был сгенерирован случайно, поэтому результат будет выглядеть не очень красиво, однако ничто не мешает вам сделать его лучше:
1 // Один цвет для каждой вершины
2 static const GLfloat g_color_buffer_data[] = {
3 0.583f, 0.771f, 0.014f,
4 0.609f, 0.115f, 0.436f,
5 0.327f, 0.483f, 0.844f,
6 0.822f, 0.569f, 0.201f,
7 0.435f, 0.602f, 0.223f,
8 0.310f, 0.747f, 0.185f,
9 0.597f, 0.770f, 0.761f,
10 0.559f, 0.436f, 0.730f,
11 0.359f, 0.583f, 0.152f,
12 0.483f, 0.596f, 0.789f,
13 0.559f, 0.861f, 0.639f,
14 0.195f, 0.548f, 0.859f,
15 0.014f, 0.184f, 0.576f,
16 0.771f, 0.328f, 0.970f,
17 0.406f, 0.615f, 0.116f,
18 0.676f, 0.977f, 0.133f,
19 0.971f, 0.572f, 0.833f,
20 0.140f, 0.616f, 0.489f,
21 0.997f, 0.513f, 0.064f,
22 0.945f, 0.719f, 0.592f,
23 0.543f, 0.021f, 0.978f,
24 0.279f, 0.317f, 0.505f,
25 0.167f, 0.620f, 0.077f,
26 0.347f, 0.857f, 0.137f,
27 0.055f, 0.953f, 0.042f,
28 0.714f, 0.505f, 0.345f,
29 0.783f, 0.290f, 0.734f,
30 0.722f, 0.645f, 0.174f,
31 0.302f, 0.455f, 0.848f,
32 0.225f, 0.587f, 0.040f,
33 0.517f, 0.713f, 0.338f,
34 0.053f, 0.959f, 0.120f,
35 0.393f, 0.621f, 0.362f,
36 0.673f, 0.211f, 0.457f,
37 0.820f, 0.883f, 0.371f,
38 0.982f, 0.099f, 0.879f
39 };Создание, привязывание и заполнения буфера такое же, как и для предыдущего буфера:
1 GLuint colorbuffer;
2 glGenBuffers(1, &colorbuffer);
3 glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
4 glBufferData(GL_ARRAY_BUFFER, sizeof(g_color_buffer_data), g_color_buffer_data, GL_STATIC_DRAW);Конфигурация тоже идентична:
1 // Второй буфер атрибутов - цвета
2 glEnableVertexAttribArray(1);
3 glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
4 glVertexAttribPointer(
5 1, // Атрибут. Здесь необязательно указывать 1, но главное, чтобы это значение совпадало с layout в шейдере..
6 3, // Размер
7 GL_FLOAT, // Тип
8 GL_FALSE, // Нормализован?
9 0, // Шаг
10 (void*)0 // Смещение
11 );Теперь, в вершинном шейдере мы имеем доступ к дополнительному буферу:
1 // Не забывайте, что значение "1" здесь должно быть идентично значению атрибута в glVertexAttribPointer
2 layout(location = 1) in vec3 vertexColor;В нашем случае мы не будем выполнять в вершинном шейдере какой-то дополнительной работы, поэтому просто передадим информацию в Фрагментный шейдер.
1 // Выходные данные. Будут интерполироваться для каждого фрагмента.
2 out vec3 fragmentColor;
3
4 void main(){
5
6 [...]
7
8 // Цвет каждой вершины будет интерполирован для получения цвета
9 // каждого фрагмента
10 fragmentColor = vertexColor;
11 }В Фрагментом шейдере мы опять объявляем fragmentColor:
1 // Интерполированные значения из вершинного шейдера
2 in vec3 fragmentColor;… и копируем это в финальный выходной цвет:
1 // Выходные данные
2 out vec3 color;
3
4 void main(){
5 // Выходной цвет = цвету, указанному в вершинном шейдере,
6 // интерполированному между 3 близлежащими вершинами.
7 color = fragmentColor;
8 }И вот, что мы получили в итоге:
Ух, выглядит как-то уродливо. Давайте посмотрим что происходит, когда мы выводим треугольник, который находится дальше, а потом треугольник, который находится ближе (far - дальний, near - ближний):
Это правильно, и теперь посмотрим как это будет в обратном порядке:
Выходит, что дальний треугольник перекрывает ближний, вместо того, чтобы быть позади. Это тоже самое, что произошло и с нашим кубом. Некоторые грани, которые должны быть невидимы были отрисованы последними и в итоге закрыли собой видимые. Здесь нам на помощь придет Буфер глубины (Z-Buffer).
Заметка 1: Если вы не видите проблемы, то попробуйте сменить позицию камеры в (4, 3, -3)
Заметка 2: Если “цвет, как и позиция является атрибутом”, то почему мы должны вводить переменную vec3 fragmentColor и работать с цветом через нее? Потому что позиция - это специальный атрибут и без него OpenGL будет просто не знать где отобразить треугольник, поэтому в вершинном шейдере есть встроенная переменная gl_Position.
Буфер глубины (Z-Buffer)
Решение проблемы заключается в хранении глубины (т. е. “Z” компоненты) каждого фрагмента в буфере и всякий раз, когда вы хотите вывести фрагмент, вам надо будет проверять, является ли он ближним или дальним.
Вы можете реализовать это сами, но гораздо более простым и элегантным решением будет использовать функционал OpenGL для этих целей:
1 // Включить тест глубины
2 glEnable(GL_DEPTH_TEST);
3 // Фрагмент будет выводиться только в том, случае, если он находится ближе к камере, чем предыдущий
4 glDepthFunc(GL_LESS);Вам также необходимо очищать буфер глубины перед каждым кадром:
1 // Очистка экрана
2 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);И этого достаточно, чтобы решить нашу проблему.
Упражнения
- Нарисуйте куб и треугольник в разных позициях. Для решения вам понадобится 2 MVP-матрицы, чтобы выполнить 2 вызова процедуры вывода в главном цикле, однако шейдер вам нужен только 1.
- Попробуйте изменить значения цветов. Например, вы можете заполнять массив цветовых атрибутов случайными значениями во время запуска программы. Можете сделать значение цвета зависимым от позиции вершины. Можете попробовать что-нибудь другое, что придет вам в голову :) Если вы не знаете как заполнить массив во время выполнения программы, то вот так это выглядит в Си:
1 static GLfloat g_color_buffer_data[12*3*3];
2 for (int v = 0; v < 12*3 ; v++){
3 g_color_buffer_data[3*v+0] = здесь укажите значение красной компоненты цвета;
4 g_color_buffer_data[3*v+1] = здесь зеленой;
5 g_color_buffer_data[3*v+2] = и наконец значение синей компоненты;
6 }- После выполнения предыдущих упражнений попробуйте сделать так, чтобы цвета менялись каждый кадр. Здесь вам понадобится вызывать glBufferData в каждом кадре. Убедитесь, что перед этим не забыли привязать соответствующий буфер (glBindBuffer)!
На этом наш урок закончен. В следующем уроке мы поговорим о текстурах.



