- Часть первая, первый квадрат
- Часть вторая, добавляем атрибуты
- Часть третья, индексный буфер
- Часть четвертая, динамика
- Часть пятая, дополнительные возможности
- Часть шестая, VAO
- Ссылки по теме
В прошлом уроке мы с вами научились рисовать простейший
примитив с использованием технологии 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 (естественно она должна быть добавлена в проект и в нее должна быть загружена текстура).
В этом случае для текстурирования будет применена первая текстура из MaterialLibrary (естественно она должна быть добавлена в проект и в нее должна быть загружена текстура).
На втором рисунке видно что текстурные координаты заданы не верно, это, как и в случае цвета, сделано специально, чтоб показать что у каждой вершины треугольников, из которых составлен квад, может быть своя текстурная координата, своя нормаль, свой цвет и другие вершинные атрибуты, не смотря на то, что координаты их вершин совпадают, фактически это не один объект а два прислоненных друг к другу треугольника. В следующем уроке мы разберем к чему это приводит.
Бинарники и исходные коды примеров: Demo2, Demo3
Комментариев нет:
Отправить комментарий