Обзор архитектуры для вывода геометрии

Автор: Евгений Рыжков Дата публикации: 26.03.2013

В отличие от предыдущих версий, в OpenGL отображение чего-либо на экране означает не просто вызов некого метода интерфейса, который сделает и подготовит видеоадаптер для нас. Чтобы отобразить что-то в OpenGL4.x нужно самостоятельно подготовить все данные для отправки их в видеоадаптер, а потом уже вызывать данный метод. Итак, рассмотрим из чего состоит графический конвейер:

Данные, из которых состоит любой объект представляются в виде вершинного буфера а так же индексного буфера. В обозначениях этого рисунка это VertexBuffer и IndexBuffer соответственно.

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

После того как данные вершин обработаны, они поступают из вершинного шейдера в пиксельный. В нем происходит обработка каждой точки полигона. То есть он закрашивается. При этом цвет закраски задается в пиксельном шейдере. Также пиксельный шейдер можно запрограммировать так, чтобы цвета брались из текстуры – так и происходит в 3d играх если модель имеет текстуру. Но предположим, что вершинный шейдер выдал для каждой из трех вершин свой цвет, каким должен получиться треугольник? Правильно, трехцветным. Пиксельный шейдер интерполирует цвета для всех входящих вершин, так что они будут плавно переходить от одной вершины к другой. Для этого не нужно добавлять в пиксельный шейдер специальный код, треугольник станет цветным только потому что выходящими данными вершинного шейдера является полигон – то есть набор из трех точек, а входящими данными для пиксельного шейдера уже не является полигон и координаты этих точек. Сам адаптер плавно передвигает «точку» рисования между вершинами постепенно заполняя треугольник(полигон) сплошным цветом и при этом для каждой маленькой суб-точки закраски вызывая пиксельный шейдер. Надо сказать что отсюда следует непреложный вывод: никогда не грузите пиксельный шейдер чрезмерно кодом – нескольких строк кода почти всегда достаточно, а при лишней загрузке пиксельного шейдера могут быть серьезные тормоза – точек на экране очень много.

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

Обзор шейдера

Практически при любом отображении чего-либо на экран, необходимо использовать шейдеры. Шейдер представляет из себя программу на языке GLSL. Этот язык подобен языку C. В предыдущих разделах мы загрузили шейдер, но совершенно его не рассмотрели. Сейчас мы займемся этим. Шейдеры делятся на два типа – вершинный и пиксельный. Рассмотрим вершинный шейдер. Его назначение – просто преобразовывать координаты вершин. Если мы не будем их преобразовывать, то все координаты будут выведены на экран в таком пространстве:

То есть, если мы отобразим треугольник, находящейся в плоскости XY, то он предстанет в неизмененном виде. Однако если треугольник будет в плоскости XZ, то он будет в виде тонкой линии или не будет виден совсем. Если же мы установим камеру, то мы сможем вращать её вокруг треугольника, при этом также будут пересчитываться его координаты для вывода в вышеуказанное пространство экрана(-1,1),(-1,1). Пересчетом координат и занимается вершинный шейдер – для этого каждая координата умножается на несколько матриц преобразования.

Стоит отметить, что если мы не будем умножать координаты точек на матрицы, или будем умножать их на еденичные матрицы – что соответствует отсутствию всякого преобразования, то это также можно использовать, например, для отображения всевозможных надписей, элементов интерфейса и другой плоской графики поверх основной 3d графики сцены. Также такая техника подойдет для игр где графика выполнена в виде спрайтов. На самом деле она настолько востребована, что системы частиц (партиклы) в какой то мере также используеют только частичное преобразование 3d координат, в результате чего частицы всегда выводятся на экран плоско, однако облака этих частиц, конечно же находятся в некотором объеме.

Но мы отступили от темы. Рассмотрим код нашего шейдера. Обратимся к вершинному шейдеру:

#version 330
 
layout (location = 0) in vec3 inPosition;
layout (location = 1) in vec3 inColor;
 
smooth out vec3 theColor;
 
void main()
{
	gl_Position = vec4(inPosition, 1.0);
	theColor = inColor;
}

Входящими данными нашего вершинного шейдера являются переменны inPosition и inColor. Это соответствует формату, который мы рассматривали ранее в уроке, каждая вершина содержит коориданату и цвет. Сама процедура вершинного шейдера называется main. Как вы можете видеть, шейдер имеет на выходе практически тот-же формат что и на входе. В него входят координаты и выходят тоже координаты, только изменененные. В следующих уроках входящие координаты будут умножаться на матрицы проекций и трансформаций, в текущем уроке мы мнимо умножаем координаты на еденичные матрицы, то есть, входящие данные inPosition равны выходящим данным gl_Position, что соответствует отсутствию всякого преобразования. Однако в следующих уроках мы установим матрицы для камеры и трансформаций и немного оживим сцену, поворачивая камеру на любые углы и рассматривая модель с различных сторон, а также приближая и отдаляя камеру.

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

#version 330
 
smooth in vec3 theColor;
out vec4 outputColor;
 
void main()
{
	outputColor = vec4(theColor, 1.0);
}

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

Материалы

http://gameinstitute.ru/