Mass Effect Galaxy - это точно EA Games? Новый потенциальный хит от Chillingo скоро в App Store
Июн 23

simulatorshotСначала думал перейти к теме освещения, но решил, что мы еще не рассмотрели множество базовых вопросов, связанных с объектами и трансформациями. Сейчас в первую очередь я имею в виду трансформацию (анимацию) двух разных объектов разными способами в одном пространстве.

Помните, как мы применяли к сцене “glTranslatef()” и “glRotatef()“? Чтобы менять условия, я пользовался вспомогательной функцией “glLoadIdentity()“. Но для вычислительных ресурсов и визуализации сцен она “дорого обходится”, поэтому сейчас мы познакомимся с более эффективным методом.

Для этой цели введем новый объект: создадим пирамиду, переместим ее с помощью “glTranslatef()” и повернем независимо от куба с помощью “glRotatef()” — не обращаясь к “glLoadIdentity()” для сброса вершин.

Добавляем в пространство пирамиду

Вместо работы с треугольником предлагаю переключиться на трехмерные объекты и добавить пирамиду (практически точно такую же, как в Гизе).

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

Итак, один сложный объект мы уже успешно создали! Приступаем ко второму.

Пирамиды не особенно проблематичны: основание-квадрат с присоединенными четырьмя треугольниками, соединяющимися над его центральной точкой. Любой интересующий объект создать гораздо проще, если мысленно разбить на простые составляющие. Меняется только количество задействуемых при его рисовании примитивов.

Запустите Xcode и откройте проект из прошлого урока. В этот раз ничего удалять не будем — добавим код и еще пару строк отредактируем.

Чтобы упростить задачу, я разобью процесс построения пирамиды на отдельные этапы. Начнем с основания:

1
2
3
4
5
6
7
const GLfloat pyramidVertices[] = {
// Пирамида состоит из 4 треугольников и квадратного основания.
// Начинаем с квадратного основания
-1.0, -1.0, 1.0,            // передняя левая сторона основания
1.0, -1.0, 1.0,             // передняя правая сторона основания
1.0, -1.0, -1.0,            // левая боковая сторона основания
-1.0, -1.0, -1.0,           // правая боковая сторона основания

Итак, мы опять создаем новые объекты. Квадрат выступает основанием, поэтому для всех вершин задана одна и та же координата Y -1.0.

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

1
2
3
4
5
// Передняя грань
-1.0, -1.0, 1.0,            // нижний левый угол треугольника
1.0, -1.0, 1.0,             // нижний правый
0.0, 1.0, 0.0,              // верхняя центральная точка — здесь встречаются
//     все вершины треугольника

Единственное отличие от треугольников, которые мы рисовали раньше, в том, что этот расположен под углом — наклонен к центру квадрата (т.е. повернут вокруг оси X).

Теперь по тому же принципу зададим три другие треугольные грани. Полная декларация пирамиды будет выглядеть так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Здесь новый код определения объекта
const GLfloat pyramidVertices[] = {
// Пирамида состоит из 4 треугольников и квадратного основания.
// Начинаем с квадратного основания
-1.0, -1.0, 1.0,            // передняя левая сторона основания
1.0, -1.0, 1.0,             // передняя правая сторона основания
1.0, -1.0, -1.0,            // левая боковая сторона основания
-1.0, -1.0, -1.0,           // правая боковая сторона основания

// Передняя грань
-1.0, -1.0, 1.0,            // нижний левый угол треугольника
1.0, -1.0, 1.0,             // нижний правый
0.0, 1.0, 0.0,              // верхняя центральная точка — здесь встречаются
//     все вершины треугольника

// Боковая грань
1.0, -1.0, -1.0,   // нижний правый угол (если смотреть спереди)
-1.0, -1.0, -1.0,           // нижний левый
0.0, 1.0, 0.0,              // верхняя центральная точка

// левая грань
-1.0, -1.0, -1.0,           // нижний угол сбоку
-1.0, -1.0, 1.0,            // нижний передний
0.0, 1.0, 0.0,              // верхняя центральная точка

// правая грань
1.0, -1.0, 1.0,             // нижний передний угол
1.0, -1.0, -1.0,            // нижний сбоку
0.0, 1.0, 0.0               // верхняя центральная точка
};

Добавьте определение пирамиды к методу “drawView” — туда же, где находится определение “cubeVerticies[]“.

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

Во-первых, это первый визуализируемый нами объект из квадратов и треугольников. Основа пирамиды представляет собой квадрат, а ее четыре стороны — треугольники. Поскольку мы вызываем “glDrawArrays()” независимо для каждого примитива, в одном определении примитивы могут быть разными. Эти моменты окончательно прояснятся, когда мы нарисуем пирамиду.

Второе. Еще раз отмечу, что все вершины я задавал в направлении против часовой стрелки. Хотя для боковой грани направление покажется противоположным, если смотреть на нее “через” переднюю, этот принцип распространяется и на нее.

В конце урока мы еще вернемся к этому вопросу и введем специальный термин из области трехмерной графики.

Рисуем пирамиду

Переходим к коду рисования куба. Первым делом удаляем строку “glLoadIdentity()” — больше она нам не нужна.

После присваивания “rota += 0.5” добавим код рисования пирамиды. Нам нужно сдвинуть ее с помощью “glTranslatef()“, а потом независимо от куба повернуть, вызвав “glRotatef()“. Другими словами, необходимо вызвать “glTranslatef()” и “glRotatef()“, не влияя на операции перемещения и рисования, применяемые к остальным рисуемым дальше объектам.

Удобный способ сделать это в OpenGL — пара функций:

1
2
3
glPushMatrix();
// Сюда код перемещения и рисования....
glPopMatrix();

OpenGL предлагает стек, в который наши матрицы копируются вызовом “glPushMatrix()“. По возможности я стараюсь избегать узкоспециализированных терминов: просто представьте объекты “pyramidVertices[]” и “cubeVertices[]” как матрицы, которые мы “заталкиваем” в стек OpenGL.

Примечание: тем, кто не уверен, что здесь понимается под стеком, советую прочесть этот материал, а лучше приобрести хорошую книгу по программированию на C или Objective C.

Итак, мы надежно сохранили данные, убрав их с дороги. Теперь как настоящие гуру OpenGL приступаем к трансформациям. Начинаем рисовать пирамиду.

1
2
3
4
5
6
glPushMatrix();
{
glTranslatef(-2.0, 0.0, -8.0);
glRotatef(rota, 1.0, 0.0, 0.0);
glVertexPointer(3, GL_FLOAT, 0, pyramidVertices);
glEnableClientState(GL_VERTEX_ARRAY);

Обратите внимание на круглые скобки после “glPushMatrix()“. В принципе, необходимости в них нет — скобки просто указывают, где матрицы добавляются, а потом освобождаются из стека.

Первые четыре строки кода после вызова “glPushMatrix()” к этому времени уже должны быть понятны. Мы вызываем “glTranslatef()“, смещая пирамиду от точки (0, 0, 0) влево и назад на 8 пунктов вглубь экрана (подальше от зрителя). После этого поворачиваем пирамиду только вокруг оси X, а не вокруг всех трех осей, как в предыдущем уроке с кубом. Последним шагом сообщаем OpenGL о данных и активируем для работы с ними.

Теперь, по окончании всех трансформаций, можно приступать к рисованию пирамиды

1
2
3
4
// Рисуем пирамиду
// Рисуем основание–квадрат
glColor4f(1.0, 0.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

Первые четыре координаты декларации пирамиды содержат ее основание. Рисуем красный квадрат методом “GL_TRIANGLE_FAN” (на основании будет и текстура, поскольку уже есть код ее наложения — см. полную реализацию “drawView” ниже). Добавляем три координаты в вершину 0 в массиве (”pyramidVerticies[0~3]“) и используем четыре вершины.

Квадрат готов, переходим к первому треугольнику:

1
2
3
// Передняя грань
glColor4f(0.0, 1.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLES, 4, 3);

Помимо цвета мы по вполне понятным причинам дополнительно меняем метод рисования на “GL_TRIANGLES“, и рисуем три вершины, начиная с элемента массива 4 (”pyramidVerticies[4~6]“). Как видим, квадрат и треугольники мирно уживаются в одной структуре данных.

Осталось нарисовать еще три треугольника:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Боковая грань
glColor4f(0.0, 0.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLES, 7, 3);

// Правая грань
glColor4f(1.0, 1.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLES, 10, 3);

// Левая грань
glColor4f(1.0, 0.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLES, 13, 3);
}
glPopMatrix();

Каждый раз мы просто меняем цвет и начальный сдвиг. Если помните, OpenGL знает, что у каждой вершины три координаты (X, Y, Z), поскольку выше мы сообщили об этом “glVertexPointer()“.

Теперь закрываем круглые скобки и вызываем “glPopMatrix()“.

Подошла очередь квадрата. Обратите внимание: один и тот же объект можно рисовать любое число раз, заключая трансформации в “glPushMatrix()” и “glPopMatrix()“.

Рисуем куб по-новому

Ниже приведен полный код рисования куба. Самое очевидное изменение — добавившиеся вызовы функций “glPushMatrix()” и “glPopMatrix()“.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
glPushMatrix();
{
glTranslatef(2.0, 0.0, -8.0);
glRotatef(rota, 1.0, 1.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, cubeVertices);
glEnableClientState(GL_VERTEX_ARRAY);

// Переднюю грань рисуем красным
glColor4f(1.0, 0.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

// Верхнюю грань рисуем зеленым
glColor4f(0.0, 1.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 4, 4);

// Боковую грань рисуем синим
glColor4f(0.0, 0.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 8, 4);

// Рисуем нижнюю грань
glColor4f(1.0, 1.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 12, 4);

// Рисуем левую грань
glColor4f(0.0, 1.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 16, 4);

// Рисуем правую грань
glColor4f(1.0, 0.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 20, 4);
}
glPopMatrix();

Еще одно изменение — передача информации на “glTranslatef()” со смещением куба в сторону от центра экрана, на два пункта вправо и на 8.0 пунктов вглубь.

Для полноты привожу полный код метода “drawView” (несколько примечаний к нему — ниже).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
- (void)drawView {

// Здесь новый код определения объекта
const GLfloat pyramidVertices[] = {
// Пирамида состоит из 4 треугольников и квадратного основания.
// Начинаем с квадратного основания
-1.0, -1.0, 1.0,            // передняя левая сторона основания
1.0, -1.0, 1.0,             // передняя правая сторона основания
1.0, -1.0, -1.0,            // левая боковая сторона основания
-1.0, -1.0, -1.0,           // правая боковая сторона основания

// Передняя грань
-1.0, -1.0, 1.0,            // нижний левый угол треугольника
1.0, -1.0, 1.0,             // нижний правый
0.0, 1.0, 0.0,              // верхняя центральная точка — здесь встречаются
//     все вершины треугольника

// Боковая грань
1.0, -1.0, -1.0,   // нижний правый угол (если смотреть спереди)
-1.0, -1.0, -1.0,           // нижний левый
0.0, 1.0, 0.0,              // верхняя центральная точка

// левая грань
-1.0, -1.0, -1.0,           // нижний угол сбоку
-1.0, -1.0, 1.0,            // нижний передний
0.0, 1.0, 0.0,              // верхняя центральная точка

// правая грань
1.0, -1.0, 1.0,             // нижний передний угол
1.0, -1.0, -1.0,            // нижний сбоку
0.0, 1.0, 0.0               // верхняя центральная точка
};

const GLfloat cubeVertices[] = {

// Определяем переднюю грань
-1.0, 1.0, 1.0,             // верхняя левая
-1.0, -1.0, 1.0,            // нижняя левая
1.0, -1.0, 1.0,             // нижняя правая
1.0, 1.0, 1.0,              // верхняя правая

// Верхняя грань
-1.0, 1.0, -1.0,            // верхняя левая (сбоку)
-1.0, 1.0, 1.0,             // нижняя левая (спереди)
1.0, 1.0, 1.0,              // нижняя правая (спереди)
1.0, 1.0, -1.0,             // верхняя правая (сбоку)

// Боковая грань
1.0, 1.0, -1.0,             // верхняя правая (если смотреть спереди)
1.0, -1.0, -1.0,            // нижняя правая
-1.0, -1.0, -1.0,           // верхняя левая
-1.0, 1.0, -1.0,            // нижняя левая

// нижняя грань
-1.0, -1.0, 1.0,
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
левая грань
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
-1.0, -1.0, 1.0,
-1.0, -1.0, -1.0,

// правая грань
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0
};

const GLshort squareTextureCoords[] = {
// Передняя грань
0, 1,       // верхняя левая
0, 0,       // нижняя левая
1, 0,       // нижняя правая
1, 1,       // верхняя правая

// Верхняя грань
0, 1,       // верхняя левая
0, 0,       // нижняя левая
1, 0,       // нижняя правая
1, 1,       // верхняя правая

// Боковая грань
0, 1,       // верхняя левая
0, 0,       // нижняя левая
1, 0,       // нижняя правая
1, 1,       // верхняя правая

// Нижняя грань
0, 1,       // верхняя левая
0, 0,       // нижняя левая
1, 0,       // нижняя правая
1, 1,       // верхняя правая

// Левая грань
0, 1,       // верхняя левая
0, 0,       // нижняя левая
1, 0,       // нижняя правая
1, 1,       // верхняя правая

// Правая грань
0, 1,       // верхняя левая
0, 0,       // нижняя левая
1, 0,       // нижняя правая
1, 1,       // верхняя правая
};

[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glViewport(0, 0, backingWidth, backingHeight);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);

glTexCoordPointer(2, GL_SHORT, 0, squareTextureCoords);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

// Здесь новый код рисования
rota += 0.5;

glPushMatrix();
{
glTranslatef(-2.0, 0.0, -8.0);
glRotatef(rota, 1.0, 0.0, 0.0);
glVertexPointer(3, GL_FLOAT, 0, pyramidVertices);
glEnableClientState(GL_VERTEX_ARRAY);

// Рисуем пирамиду
// Рисуем основание–квадрат
glColor4f(1.0, 0.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

// Передняя грань
glColor4f(0.0, 1.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLES, 4, 3);

// Боковая грань
glColor4f(0.0, 0.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLES, 7, 3);

// Правая грань
glColor4f(1.0, 1.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLES, 10, 3);

// Левая грань
glColor4f(1.0, 0.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLES, 13, 3);
}
glPopMatrix();

glPushMatrix();
{
glTranslatef(2.0, 0.0, -8.0);
glRotatef(rota, 1.0, 1.0, 1.0);
glVertexPointer(3, GL_FLOAT, 0, cubeVertices);
glEnableClientState(GL_VERTEX_ARRAY);

// Рисуем переднюю грань красной
glColor4f(1.0, 0.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

// Рисуем верхнюю грань зеленой
glColor4f(0.0, 1.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 4, 4);

// Рисуем боковую грань синей
glColor4f(0.0, 0.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 8, 4);

// Рисуем нижнюю грань
glColor4f(1.0, 1.0, 0.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 12, 4);

// Рисуем левую грань
glColor4f(0.0, 1.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 16, 4);

// Рисуем правую грань
glColor4f(1.0, 0.0, 1.0, 1.0);
glDrawArrays(GL_TRIANGLE_FAN, 20, 4);
}
glPopMatrix();

glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];

[self checkGLError:NO];
}

Заметили, что для пирамиды мы не добавляли нового массива с координатами текстур? Воспользуемся тем, что есть — для демонстрационных целей его будет достаточно.

Внесите изменения в проект (а именно, в метод “drawView“) и щелкните на кнопке “Build & Go“. Результат на экране должен быть следующим:

simulatorshot

Два объекта теперь трансформируются независимо друг от друга. Пирамида вращается вокруг оси X, а куб продолжает вращение по всем трем осям. Цвета помогут сориентироваться в гранях.

Концепция трехмерной графики: нормали

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

Все данные всегда указываются по отношению к “нормали” конкретной грани. Говоря простым языком, нормаль — это воображаемая линия, проведенная перпендикулярно к грани объекта (см. рис.)

normalvector

На изображенном выше рисунке нормаль треугольника представлена направленной от грани стрелкой. Ориентируясь на нормаль, несложно увидеть, что три вершины заданы в направлении против часовой стрелки. Работая над темой освещения, мы часто будем сталкиваться с нормалями, поэтому заранее ввожу этот термин.

Исходный код скачать можно [здесь]

Текст оригинальной статьи на английском языке [здесь]

Уважаемые читатели, данный материал был переведен и подготовлен к публикации проектом LookApp.ru, при публикации на другом сайте ссылка на LookApp.ru обязательна.

1 звезда2 звезд3 звезд4 звезд5 звезд (3 голосов, средний: 5.00 из 5)
Загрузка ... Загрузка ...


Оставьте комментарий