- •OpenGl es для новичков
- •2.5 OpenGl es 2.0. Урок 5 Шейдер преломления света……………………………………… 60
- •1.1 OpenGl es в Android, класс glSurfaceView
- •1.2 OpenGl es 1. Основы рисования для начинающих
- •Aтрибут координат вершины.
- •Цвет как атрибут вершины.
- •Координаты вектора нормали.
- •1.3 OpenGl es 1. Применение индексов при обходе вершин
- •1.4 OpenGl es 1. Освещение и материалы
- •1.5 OpenGl es 1. Двумерные текстуры
- •1.6 OpenGl es 1. Текстурирование на примере
- •1.7 OpenGl es 1. Как сделать текстуры прозрачными
- •1.8 OpenGl es 1. Как наложить несколько текстур на один полигон
- •1.9 OpenGl es 1. Текстуры в движении
- •1.10 OpenGl es 1. Зеркальное отражение при помощи кубических текстур
- •2.1 OpenGl es 2.0. Урок первый-Шейдеры Введение в шейдеры.
- •Получение матриц.
- •Создание шейдерного объекта в OpenGl es 2.0
- •Передача данных униформ в шейдеры.
- •Передача атрибутов вершин в вершинный шейдер.
- •Класс шейдерного объекта.
- •2.2 OpenGl es 2.0. Урок второй - Освещение в шейдере
- •Фоновое (ambient) освещение.
- •Диффузное (diffuse) или рассеянное освещение.
- •Зеркальное (specular) или бликовое освещение.
- •Коды шейдеров.
- •Практика. Освещение плоской поверхности.
- •2.3 OpenGl es 2.0. Урок третий - Двумерные текстуры Загрузка текстуры из файла.
- •Доступ к текстурам из шейдера.
- •Получение цвета пикселя из текстуры.
- •Практика. Текстурированный квадрат.
- •Включаем свет.
- •2.4 OpenGl es 2.0. Урок 4. Гладкие поверхности. Полигональная сетка.
- •Расчет нормалей.
- •Разбиваем сетку на треугольники и рисуем поверхность.
- •Полный код класса рендерера, рисующего поверхность.
- •2.5 OpenGl es 2.0. Урок 5. Шейдер преломления света
Координаты вектора нормали.
Что такое вектор нормали ? Это вектор единичной длины, перпендикулярный к поверхности в данной точке этой поверхности. Вектор нормали, как и всякий другой вектор имеет три проекции на оси координат X,Y,Z, которые называют координатами вектора. Координаты вектора нормали присваиваются каждой вершине отдельно и являются её атрибутом. Вектор нормали очень важен для правильного расчета освещения поверхности, т.к. определяет направление отражённого света. Как можно рассчитать вектор нормали ? Для расчета нормали необходимо иметь три точки на поверхности. Из трёх точек можно составить два вектора, которые всегда будут лежать в одной плоскости. Проведем из точки 1 в точку 2 вектор A, из точки 1 в точку 3 вектор B. Вектор N, перпендикулярный A и B рассчитывается как векторное произведение N=[AxB]:
Nx=Ay*Bz-By*Az=(y2-y1)*(z3-z1)-(y3-y1)*(z2-z1) Ny=Bx*Az-Ax*Bz=(x3-x1)*(z2-z1)-(x2-x1)*(z3-z1) Nz=Ax*By-Bx*Ay=(x2-x1)*(y3-y1)-(x3-x1)*(y2-y1)
Далее нужно вычислить длину вектора N и его координаты Nx,Ny,Nz поделить на длину, т.е. нормализовать вектор N. Однако мы не будем заниматься нормализацией, т.к. при этом требуется вычисление квадратного корня, а это операция затратная по времени исполнения.
За нас нормализацию выполнит OpenGL. Для этого достаточно выполнить команду glEnable(GL10.GL_NORMALIZE), т.е. разрешить автоматическую нормализацию внутри графического конвейера.
Предположим, что у нас имеется треугольник, состоящий из вершин с координатами x1,y1,z1 первой вершины, x2,y2,z2-второй вершины, x3,y3,z3-третьей вершины. Присвоение нормалей вершинам производится аналогично координатам точек. Сначала создадим для треугольника буфер необходимой длины:
FloatBuffer normalBuffer; ByteBuffer bb = ByteBuffer.allocateDirect(36); bb.order(ByteOrder.nativeOrder()); normalBuffer = bb.asFloatBuffer();
Затем вычислим координаты вектора нормали исходя из координат точек треугольника:
float nx=(y2-y1)*(z3-z1)-(y3-y1)*(z2-z1); float ny=(x3-x1)*(z2-z1)-(x2-x1)*(z3-z1); float nz=(x2-x1)*(y3-y1)-(x3-x1)*(y2-y1);
Запишем координаты вектора нормали в буфер. Поскольку нормаль является одинаковой для всех вершин треугольника повторим запись в буфер три раза:
normalBuffer.position(0); for (int i=1;i<4;i++){ normalBuffer.put(nx); normalBuffer.put(ny); normalBuffer.put(nz); } normalBuffer.position(0);
Далее включаем использование массивов нормалей: gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); И передаем наш буфер нормалей normalBuffer в OpenGL: gl.glNormalPointer(GL10.GL_FLOAT,0,normalBuffer);
При рисовании полигонов нужно обязательно соблюдать правило: порядок обхода вершин при записи в буферы должен быть одинаков для координат вершин, цветов, векторов нормалей и координат текстур.
Как уже отмечалось, вектор нормали нужен для правильного расчета освещения поверхности. Освещение, а также текстуры будут рассмотрены отдельно в следующих статьях.
1.3 OpenGl es 1. Применение индексов при обходе вершин
Применение команды glDrawArrays обязывает нас упорядочивать массивы координат вершин, нормалей и текстур в строго определённом порядке в соответствии с выбранным правилом обхода точек GL_TRIANGLES, GL_TRIANGLE_STRIP или GL_TRIANGLE_FAN. Это не всегда бывает удобно. Поэтому в OpenGL существует возможность разделить данные вершин и правила обхода по разным массивам. Специально подготовленный массив, в котором хранится порядок обхода вершин, называют массивом индексов. Рассмотрим применение индексов на простом примере. Допустим, что у нас имеется два ряда горизонтальных точек. Верхний ряд состоит из точек 0,1,2,3, нижний ряд из точек 4,5,6,7. Обозначим координаты вершин точки 0 как x0,y0,z0, точки 1 - x1,y1,z1, точки 2 - x2,y2,z2 и так далее до точки 7. Предположим, что нам удобно хранить координаты вершин в следующем порядке - 0,1,2,3,4,5,6,7. Сформируем массив координат и передадим его в буфер:
float [] vertexArray={x0,y0,z0, x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4, x5,y5,z5, x6,y6,z6, x7,y7,z7};
ByteBuffer b1 = ByteBuffer.allocateDirect(12*8); b1.order(ByteOrder.nativeOrder()); vertexBuffer = b1.asFloatBuffer(); vertexBuffer.put(vertexArray); vertexBuffer.position(0);
Для примера выберем правило GL_TRIANGLE_STRIP, в соответствии которым лента треугольников формируется в следующем порядке: сначала рисуется треугольник из точек 0-4-1, далее 1-4-5, 1-5-2, 2-5-6, 2-6-3, 3-6-7:
Массив индексов представляет из себя последовательность номеров точек, понятную для выбранного правила обхода. Поскольку мы используем правило GL_TRIANGLE_STRIP, которое само добавляет промежуточные точки, из массива индексов нужно выбросить лишние вершины, т.е. вместо порядка 0,4,1, 1,4,5, 1,5,2, 2,5,6, 2,6,3, 3,6,7 необходимо указать номера точек в следующей последовательности: 0,4,1,5,2,6,3,7.
Формат номеров точек в индексном массиве может быть только byte или short, целые числа типа int для индексов в OpenGL ES не используются. Определимся с размером буфера индексов. Каждый индекс как число типа short занимает в памяти два байта. Всего у нас 8 точек. Поэтому буфер должен быть размером 16 байт. Сформируем массив индексов и передадим его в буфер:
short [] indexArray={0,4,1,5,2,6,3,7};
ByteBuffer b2 = ByteBuffer.allocateDirect(16); b2.order(ByteOrder.nativeOrder()); ShortBuffer indexBuffer = b2.asShortBuffer();
indexBuffer.position(0); indexBuffer.put(indexArray); indexBuffer.position(0);
Далее мы должны разрешить использование массивов координат вершин и передать их в OpenGL:
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // указываем программе, что буфер с именем vertexBuffer является буфером координат вершин gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);
Для рисования с использованием массивов индексов команда glDrawArrays не используется, вместо неё применяется другая команда glDrawElements:
gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 8,
GL10.GL_UNSIGNED_SHORT,indexBuffer);
В качестве первого аргумента задается выбранное правило, в нашем примере это GL_TRIANGLE_STRIP. Второй аргумент - это количество элементов в индексном массиве, т.е. восемь. Третий аргумент - формат чисел индексов, в нашем случае short. Последний аргумент буфер, в который записан индексный массив.
Если вместо правила GL_TRIANGLE_STRIP для рисования ленты использовать GL_TRIANGLES, которое рисует каждую тройку вершин как отдельный треугольник, то мы должны указывать в массиве индексов все промежуточные точки. В этом случае количество индексов возрастет до 18 значений:
short [] indexArray={ 0,4,1, 1,4,5, 1,5,2, 2,5,6, 2,6,3, 3,6,7 };
ByteBuffer b2 = ByteBuffer.allocateDirect(2*18); b2.order(ByteOrder.nativeOrder());ShortBuffer indexBuffer = b2.asShortBuffer();
indexBuffer.position(0); indexBuffer.put(indexArray);
indexBuffer.position(0);
и соответственно изменится формат команды glDrawElements:
gl.glDrawElements(GL10.GL_TRIANGLES, 18,
GL10.GL_UNSIGNED_SHORT,indexBuffer);
Практика показывает, что использование индексов заметно увеличивает скорость отрисовки кадров при большом количестве вершин. Поэтому, по возможности, нужно заменять команду glDrawArrays на glDrawElements.
