вторник, 21 февраля 2012 г.

Основы VBO в OpenGL. Часть вторая, добавляем атрибуты.


  1. Часть первая, первый квадрат
  2. Часть вторая, добавляем атрибуты
  3. Часть третья, индексный буфер
  4. Часть четвертая, динамика
  5. Часть пятая, дополнительные возможности
  6. Часть шестая, VAO
  7. Ссылки по теме
В прошлом уроке мы с вами научились рисовать простейший примитив с использованием технологии VBO, сейчас самое время добавить к нашему квадрату немного цвета, делается это через присоединение к VBO еще одного вершинного атрибута, содержащего цвет каждой из вершин.

Как я уже писал выше – все буферы вершинных атрибутов задаются однообразно – сгенерировали идентификатор, забиндили буфер и передали в него данные. Сейчас мы это и реализуем для буфера цвета. Как вы знаете – цвет у нас представлен в виде 3-х компонент – R,G,B, данные эти могут содержаться как в виде байтов, так и нормированными к единицы, принципиальной разницы в создании буферов нет, за исключением типа хранимых данных.

Итак, объявим массив цветов для 6 вершин (да, у квадрата вершин у нас всего 4, но мы сейчас рисуем квадрат в виде двух треугольников, а на треугольник требуется 3 вершины, в итоге у нас получается 6 вершин, пре из которых у нас совпадают). Так же нам понадобится еще один идентификатор для буфера цвета, объявим и его тоже.
Листинг 8:
1
2
3
var
     ColorBuff: array of TRGBTriple;
     cId: integer;
Теперь внесем некоторые изменения в процедуру инициализации буфера VBO:
Листинг 9:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
procedure VBOInit;
var Count: integer;
begin
  Count:=high(VertexBuffer)+1;
  glGenBuffers( 1, @vId );
  glBindBuffer(GL_ARRAY_BUFFER, vId );
  glBufferData(GL_ARRAY_BUFFER, sizeof(GLFLoat)*3*Count, @VertexBuffer[0], GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER,0);
  glGenBuffers( 1, @cId );
  glBindBuffer(GL_ARRAY_BUFFER, cId );
  glBufferData(GL_ARRAY_BUFFER, sizeof(GLUByte)*3*Count, @ColorBuffer[0], GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER,0);
end;

Как видите – особого отличия от создания вершинного буфера здесь нет, за исключением изменившихся имен идентификатора и массива, а так же изменившегося типа данных на GLUByte,
это говорит о том, что каждая из компонент цвета занимает ровно один байт.
Теперь изменим процедуру рисования, добавив туда использования буфера цвета:
Листинг 10:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
procedure VBODraw;
var Count:integer;
begin
  Count:=high(VertexBuffer)+1;
  glEnableClientState( GL_VERTEX_ARRAY );
  glEnableClientState( GL_COLOR_ARRAY );
  glBindBuffer( GL_ARRAY_BUFFER, cId );
  glColorPointer( 3, GL_UNSIGNED_BYTE, 0, nil );
  glBindBuffer( GL_ARRAY_BUFFER, vId );
  glVertexPointer( 3, GL_FLOAT, 0, nil );
  glDrawArrays(GL_TRIANGLES , 0, Count);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_COLORARRAY);
end;

Как видите – изменения минимальны, мы активируем еще одно состоянии OpenGL используя константу GL_COLOR_ARRAY, биндим наш буфер по идентификатору cId и указываем GPU что в этом буфере у нас будет содержаться информация о цвете каждой из вершин. Ну и после рисования нужно не забыть выключить соответствующее состояние (13-я строка).

Несколько важных замечаний – gl*Pointer нужно использовать непосредственно после бинда соответствующего буфера, иначе бинд второго буфера автоматически выполняет unBind текущего.

Ну теперь остались мелочи – заполнить соответствующий буфер информацией о цвете, делается это все там же, в событии формы onCreate:
Листинг 11:
 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
function SetRGB(r,g,b:byte):TRGBTriple;
begin
  Result.rgbtBlue:=b;  Result.rgbtGreen:=g;   Result.rgbtRed:=r;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
   Setlength(VertexBuffer,6);//Создаем буффер под 6 вершин
   Setlength(ColorBuffer,6);//Создаем буффер под цвет для каждой из вершин
   //Создаем квадрат состоящий из двух треугольников
   //Первый треугольник
   VertexBuffer[0].X:=-1; VertexBuffer[0].Y:=1; VertexBuffer[0].Z:=0;
   VertexBuffer[1].X:=-1; VertexBuffer[1].Y:=-1;VertexBuffer[1].Z:=0;
   VertexBuffer[2].X:= 1; VertexBuffer[2].Y:=1; VertexBuffer[2].Z:=0;
   ColorBuffer[0]:=SetRGB(255,0,0);
   ColorBuffer[1]:=SetRGB(0,255,0);
   ColorBuffer[2]:=SetRGB(0,0,255);
   //Второй треугольник
   VertexBuffer[3].X:= 1; VertexBuffer[3].Y:=1; VertexBuffer[3].Z:=0;
   VertexBuffer[4].X:=-1; VertexBuffer[4].Y:=-1;VertexBuffer[4].Z:=0;
   VertexBuffer[5].X:= 1; VertexBuffer[5].Y:=-1;VertexBuffer[5].Z:=0;
   ColorBuffer[3]:=SetRGB(0,0,255);
   ColorBuffer[4]:=SetRGB(0,255,255);
   ColorBuffer[5]:=SetRGB(255,255,0);
   //Инициализируем наш буфер
   VBOInit;
   Ready:=true;
end;
Здесь ничего принципиального нет, единственное – для экономии места создал функцию SetRGB, чтоб заполнять структуру TRGBTriple. Так же изменил цвета для совпадающих вершин, чтоб вы могли видеть и понимали что хоть эти вершины и находятся в одном месте, но принадлежат совсем разным треугольникам. В дальнейшем мы рассмотрим вариант, исключающий задание совпадающих вершин, ну а пока – внесем еще одно изменение в код, в этот раз в событие onRender GLDirectOpenGL:
Листинг 12:
1
2
3
4
5
6
7
8
procedure TForm1.GLDirectOpenGL1Render(Sender: TObject;
  var rci: TRenderContextInfo);
begin
  if not Ready then exit;
  glEnable(GL_COLOR_MATERIAL);
  VBODraw;
  glDisable(GL_COLOR_MATERIAL);
end;
По умолчанию в сцене отключено использование цвета вершин, потому нам придется его включить соответствующей командой:  glEnable(GL_COLOR_MATERIAL). По завершению рисования рекомендуется подчищать за собой, потому отключим использование цвета.
Так же нужно не забыть удалять буфер цвета вместе с удалением вершинного буфера, для этого допишем функцию VBOFree:
Листинг 13:
1
2
3
4
5
6
procedure VBOFree;
begin
  glBindBuffer( GL_ARRAY_BUFFER, 0 );
  glDeleteBuffers(1,@vId);
  glDeleteBuffers(1,@cId);
end;

Все, теперь можно запускать и смотреть на результат, если вы все сделали правильно, то должно получиться примерно то же что и на картинке ниже:
Рис.2. Использование буфера цвета
Ну и далее, с минимальными комментариями, мы добавим сюда еще два наиболее часто используемых буфера – буфер нормалей и буфер текстурных координат.

Как мы знаем нормаль так же как и вершина имеет 3 компоненты, потому для создания буфера нормалей мы так же будем использовать тип данных TVertex, а вот текстурные координаты у нас могут иметь как одну так и две, три и даже 4 текстурных координаты. Не смотря на все разнообразие задаются эти буферы идентично, отличие составляет лишь количество компонентов в массиве.

Итак, допишем пару глобальных переменных:
Листинг 14:
1
2
3
4
5
6
7
8
Type
  TTexCoord2 = record
     s,t:single; //для примера возьмем двухкомпонентный вектор
  end;
var
   NormalsBuff: array of TVertex;
   TexCoordsBuff: array of TTextCoord2;
   nId, tId: integer;
Думаю эта часть понятна, теперь переходим к функции инициализации VBOInit, допишем туда инициализацию еще двух буферов:
Листинг 15:
1
2
3
4
5
6
7
glBindBuffer(GL_ARRAY_BUFFER, nId );
  glBufferData(GL_ARRAY_BUFFER, sizeof(GLFLoat)*3*Count, @NormalsBuff[0], GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER,0);
  glGenBuffers( 1, @tId );
  glBindBuffer(GL_ARRAY_BUFFER, tId );
  glBufferData(GL_ARRAY_BUFFER, sizeof(GLFLoat)*2*Count, @TexCoordsBuff[0], GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER,0);
Как видите – отличий от создания вершинного буфера практически нет, исключение число «2» при передачи данных о текстурных координатах, почему именно 2 думаю понятно.

Теперь нужно сказать OpenGL что эти буферы нужно использовать при рисовании, для этого допишем соответствующий код в функцию VBODraw:
Листинг 16:
1
2
3
4
5
6
7
8
9
  glEnableClientState( GL_NORMAL_ARRAY );
  glBindBuffer( GL_ARRAY_BUFFER, nId );
  glNormalPointer(GL_FLOAT, 0, nil );
  glEnableClientState( GL_TEXTURE_COORD_ARRAY );
  glBindBuffer( GL_ARRAY_BUFFER, tId );
  glTexCoordPointer(2, GL_FLOAT, 0, nil );
....................
  glDisableClientState(GL_NORMAL_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY); 
Строки 1-6 пишутся до glDrawArrays, а 7-8, выключающие соответствующие состояния - после.
Тут тоже ничего принципиально нового нет, за исключением появившихся 2-х новых констант и соответствующим им функций-указателей. Единственное что стоит отметить – функция  glNormalPointer не требует в качестве параметра количество компонент, так как оно всегда равно 3

Добавляем еще пару строк в функцию удаления VBOFree:
  glDeleteBuffers(1,@nId);
  glDeleteBuffers(1,@tId);

И пишем кучу кода в событии onCreate, заполняющего соответствующие буферы данными. Из-за объема я его приводить не буду, его можно найти в исходниках, прилагаемых в конце статьи.
Рис.3. Установка нормалей и текстурных координат
Единственное что еще можно упомянуть – для того, чтобы применить текстуру к нашему объекту достаточно в событии onRender вместо glEnable/glDisable(GL_COLOR_MATERIAL) добавить активацию текстуры, средствами GLScene это делается через библиотеку материалов:
    MaterialLibrary.Materials[0].Apply(rci); {.unApply(rci)}
В этом случае для текстурирования будет применена первая текстура из MaterialLibrary (естественно она должна быть добавлена в проект и в нее должна быть загружена текстура).

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

Бинарники и исходные коды примеров: Demo2, Demo3

Комментариев нет:

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