Студопедия — Разбиваем сетку на треугольники и рисуем поверхность.
Студопедия Главная Случайная страница Обратная связь

Разделы: Автомобили Астрономия Биология География Дом и сад Другие языки Другое Информатика История Культура Литература Логика Математика Медицина Металлургия Механика Образование Охрана труда Педагогика Политика Право Психология Религия Риторика Социология Спорт Строительство Технология Туризм Физика Философия Финансы Химия Черчение Экология Экономика Электроника

Разбиваем сетку на треугольники и рисуем поверхность.






Далее мы будем рисовать нашу поверхность в одном проходе, т.е. с использованием одной команды glDrawElements и правила обхода вершин GL_TRIANGLE_STRIP. Разобьем нашу сетку на ленты из треугольников. В нашем примере сначала идет лента слева-направо и вершины передаются в OpenGL в следующем порядке 0,5,1,6,2,7,3,8,4,9. Правило GL_TRIANGLE_STRIP автоматически создает ряд треугольников 0-5-1, 1-5-6, 1-6-2, 2-6-7, 2-7-3, 3-7-8, 3-8-4, 4-8-9. Порядок перечисления вершин выбран так, чтобы обход выполнялся против часовой стрелки и верхняя сторона сетки считалась лицевой.

Дойдя до 9-ой вершины мы переходим к ленте справа-налево:

 

Нам удобно перечислять вершины в таком же порядке 9,14, 8,13, 7,12, т.е. сверху-вниз. Однако при этом топология правила GL_TRIANGLE_STRIP нарушится и обход будет выполняться по часовой стрелке. Для того чтобы склеить две ленты я продублировал 9-ю вершину между лентами и мысленно продолжил ряд. На рисунке добавленная вершина отмечена красным цветом:

Теперь порядок перечисления вершин будет такой:
0,5,1,6,2,7,3,8,4,9, 9, 9,14,8,13,7,12,6,11,5,10, 10, 10,15,11,16,12,17,13,18,14,19
Красным цветом отмечены добавленные вершины.

Таким образом, чтобы сделать единую цепь вершин для правила GL_TRIANGLE_STRIP нужно дублировать последнюю вершину в каждой ленте. Порядок перечисления вершин называют массивом индексов и передают OpenGL в виде буфера. Создадим такой массив:

// временный массив индексов
short[] index;
// 2*(imax+1) - количество индексов в ленте
// jmax - количество лент
// (jmax-1) - добавленные индексы для связки лент
// размер индексного массива
sizeindex=2*(imax+1)*jmax + (jmax-1);
index = new short[sizeindex];

Для того, чтобы получить порядковый номер вершины по известным номерам i и j введем дополнительную функцию chain:

private short chain(int j, int i){
return (short) (i+j*(imax+1));

}
Например, для i=3, j=2 функция chain возвратит 13, т.к. в нашем примере imax=4.
Приступим к расчету массива индексов:
int k=0;
int j=0;
while (j < jmax) {
// лента слева направо
for (int i = 0; i <= imax; i++) {
index[k] = chain(j,i);
k++;
index[k] = chain(j+1,i);
k++;
}
if (j < jmax-1){
// вставим хвостовой индекс для связки
index[k] = chain(j+1,imax);
k++;
}
// переводим ряд
j++;

// проверяем достижение конца
if (j < jmax){
// лента справа налево
for (int i = imax; i >= 0; i--) {
index[k] = chain(j,i);
k++;
index[k] = chain(j+1,i);
k++;
}
if (j < jmax-1){
// вставим хвостовой индекс для связки
index[k] = chain(j+1,0);
k++;
}
// переводим ряд
j++;
}
}

// буфер индексов - тип short содержит 2 байта
ByteBuffer bi = ByteBuffer.allocateDirect(sizeindex * 2);
bi.order(ByteOrder.nativeOrder());
indexBuffer = bi.asShortBuffer();
// заполняем буфер индексов
indexBuffer.put(index);
indexBuffer.position(0);
// уничтожаем временный массив индексов,
// т.к. в дальнейшем нужен только буфер индексов
index = null;

Для того, чтобы нарисовать нашу поверхность достаточно вызвать команду:
GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, sizeindex,
GLES20.GL_UNSIGNED_SHORT, indexBuffer);

Полный код класса рендерера, рисующего поверхность.

Для примера приведу полный код класса MyClassRenderer, который создает сетку размером 50x50 и рисует холм.

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;

public class MyClassRenderer implements GLSurfaceView.Renderer{
// интерфейс GLSurfaceView.Renderer содержит три метода onDrawFrame, onSurfaceChanged, onSurfaceCreated
// которые должны быть переопределены текущий контекст
private Context context;
//координаты камеры
private float xСamera, yCamera, zCamera;
//координаты источника света
private float xLightPosition, yLightPosition, zLightPosition;
//матрицы
private float[] modelMatrix;
private float[] viewMatrix;
private float[] modelViewMatrix;
private float[] projectionMatrix;
private float[] modelViewProjectionMatrix;
//размеры сетки
private int imax=49;
private int jmax=49;
//размер индексного массива
private int sizeindex;
//начальная координата x
private float x0=-1f;
//начальная координата z
private float z0=-1f;
//шаг сетки по оси x
private float dx=0.04f;
//шаг сетки по оси z
private float dz=0.04f;
// массив для хранения координаты x
private float [] x;
// массив для хранения координаты y
private float [][] y;
//массив для хранения координаты z
private float [] z;
//массив для хранения координат вершин для записи в буфер
private float [] vertex;
//массивы для хранения координат вектора нормали
private float [][] normalX;
private float [][] normalY;
private float [][] normalZ;
//массив для хранения координат вектора нормали для записи в буфер
private float [] normal;
//буферы для координат вершин и нормалей
private FloatBuffer vertexBuffer, normalBuffer;
//буфер индексов
private ShortBuffer indexBuffer;
//шейдерный объект
private Shader mShader;
//------------------------------------------------------------------------------------------
//конструктор
public MyClassRenderer(Context context) {
// запомним контекст

// он нам понадобится в будущем для загрузки текстур
this.context=context;
//координаты точечного источника света
xLightPosition=5f;
yLightPosition=5f;
zLightPosition=5f;
//матрицы
modelMatrix=new float[16];
viewMatrix=new float[16];
modelViewMatrix=new float[16];
projectionMatrix=new float[16];
modelViewProjectionMatrix=new float[16];
//мы не будем двигать объекты поэтому сбрасываем модельную матрицу на единичную
Matrix.setIdentityM(modelMatrix, 0);
//координаты камеры
xСamera=0.3f;
yCamera=1.7f;
zCamera=1.5f;
//пусть камера смотрит на начало координат и верх у камеры будет вдоль оси Y

//зная координаты камеры получаем матрицу вида
Matrix.setLookAtM(viewMatrix, 0, xСamera, yCamera, zCamera, 0, 0, 0, 0, 1, 0);
// умножая матрицу вида на матрицу модели получаем матрицу модели-вида
Matrix.multiplyMM(modelViewMatrix, 0, viewMatrix, 0, modelMatrix, 0);
// создаем массивы
x=new float [imax+1];
z=new float [jmax+1];
y=new float [jmax+1][imax+1];
vertex=new float[(jmax+1)*(imax+1)*3];
normalX=new float[jmax+1][imax+1];
normalY=new float[jmax+1][imax+1];
normalZ=new float[jmax+1][imax+1];
normal=new float[(jmax+1)*(imax+1)*3];
//заполним массивы x и z координатами сетки
for (int i=0; i<=imax; i++){
x[i]=x0+i*dx;

}
for (int j=0; j<=jmax; j++){
z[j]=z0+j*dz;

}
//создадим буфер для хранения координат вершин он заполняется в методе getVertex()
ByteBuffer vb = ByteBuffer.allocateDirect((jmax+1)*(imax+1)*3*4);
vb.order(ByteOrder.nativeOrder());
vertexBuffer = vb.asFloatBuffer();
vertexBuffer.position(0);
//создадим буфер для хранения координат векторов нормалей он заполняется в методе getNormal()
ByteBuffer nb = ByteBuffer.allocateDirect((jmax+1)*(imax+1)*3*4);
nb.order(ByteOrder.nativeOrder());
normalBuffer = nb.asFloatBuffer();
normalBuffer.position(0);
//индексы временный массив индексов
short[] index;

// 2*(imax+1) - количество индексов в ленте

// jmax - количество лент

// (jmax-1) - добавленные индексы для связки лент
sizeindex=2*(imax+1)*jmax + (jmax-1);
index = new short[sizeindex];
// расчет массива индексов для буфера
int k=0;
int j=0;
while (j < jmax) {
// лента слева направо

for (int i = 0; i <= imax; i++) {

index[k] = chain(j,i);

k++;

index[k] = chain(j+1,i);

k++;

}

if (j < jmax-1){

// вставим хвостовой индекс для связки

index[k] = chain(j+1,imax);

k++;

}

// переводим ряд

j++;


// проверяем достижение конца

if (j < jmax){

// лента справа налево

for (int i = imax; i >= 0; i--) {

index[k] = chain(j,i);

k++;

index[k] = chain(j+1,i);

k++;

}

if (j < jmax-1){

// вставим хвостовой индекс для связки

index[k] = chain(j+1,0);

k++;

}

// переводим ряд

j++;

}

}
// буфер индексов - тип short содержит 2 байта
ByteBuffer bi = ByteBuffer.allocateDirect(sizeindex * 2);
bi.order(ByteOrder.nativeOrder());
indexBuffer = bi.asShortBuffer();
// заполняем буфер индексов
indexBuffer.put(index);
indexBuffer.position(0);
// уничтожаем временный массив индексов, т.к. в дальнейшем нужен только буфер индексов
index = null;
//начальное заполнение буферов вершин и нормалей
getVertex();
getNormal();
}//конец конструктора
//------------------------------------------------------------------------------------------
// вспомогательная функция
// возвращает порядковый номер вершины по известным j и i
private short chain(int j, int i){
return (short) (i+j*(imax+1));
}
//------------------------------------------------------------------------------------------
//метод выполняет расчет координат вершин
private void getVertex(){
// заполним массив Y значениями функции
for (int j=0; j<=jmax; j++){
for (int i=0; i<=imax; i++){

y[j][i]=(float)Math.exp(-3*(x[i]*x[i]+z[j]*z[j]));

}

}
// заполним массив координат vertex
int k=0;
for (int j=0; j<=jmax; j++){
for (int i=0; i<=imax; i++){

vertex[k]=x[i];

k++;

vertex[k]=y[j][i];

k++;

vertex[k]=z[j];

k++;

}

}
//перепишем координаты вершин из массива vertex в буфер координат вершин
vertexBuffer.put(vertex);
vertexBuffer.position(0);
}//конец метода
//------------------------------------------------------------------------------------------

 

//метод выполняет расчет векторов нормалей по известным координатам вершин
private void getNormal(){
for (int j=0; j<jmax; j++){
for (int i=0; i<imax; i++){

normalX [j] [i] = - (y [j] [i+1] - y [j] [i]) * dz;

normalY [j] [i] = dx * dz;

normalZ [j] [i] = - dx * (y [j+1] [i] - y [j] [i]);

}

}
//нормаль для i=imax
for (int j=0; j<jmax; j++){
normalX [j] [imax] = (y [ j ] [ imax -1] - y [ j ] [ imax]) * dz;

normalY [j] [imax] = dx * dz;

normalZ [j] [imax] = - dx * (y [ j+1 ] [ imax] - y [ j ] [ imax ]);

}
//нормаль для j=jmax
for (int i=0; i<imax; i++){
normalX [jmax] [ i ] = - (y [ jmax ] [ i+1 ] - y [ jmax ] [ i ]) * dz;

normalY [jmax] [ i ] = dx * dz;

normalZ [jmax] [ i ] = dx * (y [ jmax-1 ] [ i ] - y [ jmax ] [ i ]);

}
//нормаль для i=imax и j=jmax
normalX [jmax] [ imax ]= (y [ jmax] [ imax-1] - y [ jmax] [imax]) * dz;
normalY [jmax] [ imax ] = dx * dz;
normalZ [jmax] [ imax ] = dx * (y [jmax-1] [imax] - y[jmax ] [imax]);
//переписываем координаты вектора нормали в одномерный массив normal
int k=0;
for (int j=0; j<=jmax; j++){
for (int i=0; i<=imax; i++){

normal[k]=normalX[j][i];

k++;

normal[k]=normalY[j][i];

k++;

normal[k]=normalZ[j][i];

k++;

}

}
//отправляем одномерный массив normal в буфер
normalBuffer.put(normal);
normalBuffer.position(0);
} // конец метода
//------------------------------------------------------------------------------------------
//метод, который срабатывает при изменении размеров экрана
//в нем мы получим матрицу проекции и матрицу модели-вида-проекции
public void onSurfaceChanged(GL10 unused, int width, int height) {
// устанавливаем glViewport
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
float k=0.055f;
float left = -k*ratio;
float right = k*ratio;
float bottom = -k;
float top = k;
float near = 0.1f;
float far = 10.0f;
// получаем матрицу проекции
Matrix.frustumM(projectionMatrix, 0, left, right, bottom, top, near, far);
// матрица проекции изменилась, поэтому нужно пересчитать матрицу модели-вида-проекции
Matrix.multiplyMM(modelViewProjectionMatrix, 0, projectionMatrix, 0, modelViewMatrix, 0);
} //конец метода
//------------------------------------------------------------------------------------------
//метод, который срабатывает при создании экрана здесь мы создаем шейдерный объект
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
//включаем тест глубины
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
//включаем отсечение невидимых граней
GLES20.glEnable(GLES20.GL_CULL_FACE);
//включаем сглаживание текстур, это пригодится в будущем
GLES20.glHint(GLES20.GL_GENERATE_MIPMAP_HINT, GLES20.GL_NICEST);
//простые шейдеры для освещения
String vertexShaderCode=
"uniform mat4 u_modelViewProjectionMatrix;"+

"attribute vec3 a_vertex;"+

"attribute vec3 a_normal;"+

"varying vec3 v_vertex;"+

"varying vec3 v_normal;"+

"void main() {"+

"v_vertex=a_vertex;"+

"vec3 n_normal=normalize(a_normal);"+

"v_normal=n_normal;"+

"gl_Position = u_modelViewProjectionMatrix * vec4(a_vertex,1.0);"+

"}";

 

String fragmentShaderCode=
"precision mediump float;"+

"uniform vec3 u_camera;"+

"uniform vec3 u_lightPosition;"+

"varying vec3 v_vertex;"+

"varying vec3 v_normal;"+

"void main() {"+

"vec3 n_normal=normalize(v_normal);"+

"vec3 lightvector = normalize(u_lightPosition - v_vertex);"+

"vec3 lookvector = normalize(u_camera - v_vertex);"+

"float ambient=0.1;"+

"float k_diffuse=0.7;"+

"float k_specular=0.3;"+

"float diffuse = k_diffuse * max(dot(n_normal, lightvector), 0.0);"+

"vec3 reflectvector = reflect(-lightvector, n_normal);"+

"float specular=k_specular*pow(max(dot(lookvector,reflectvector),0.0),40.0);"+

"vec4 one=vec4(1.0,1.0,1.0,1.0);"+

"vec4 lightColor=(ambient+diffuse+specular)*one;"+

"gl_FragColor=lightColor;"+

"}";

//создадим шейдерный объект
mShader=new Shader(vertexShaderCode, fragmentShaderCode);
//свяжем буфер вершин с атрибутом a_vertex в вершинном шейдере
mShader.linkVertexBuffer(vertexBuffer);
//свяжем буфер нормалей с атрибутом a_normal в вершинном шейдере
mShader.linkNormalBuffer(normalBuffer);
//связь атрибутов с буферами сохраняется до тех пор,

//пока не будет уничтожен шейдерный объект
}//конец метода
//------------------------------------------------------------------------------------------
//метод, в котором выполняется рисование кадра
public void onDrawFrame(GL10 unused) { //передаем в шейдерный объект матрицу модели-вида-проекции
mShader.linkModelViewProjectionMatrix(modelViewProjectionMatrix);
//передаем в шейдерный объект координаты камеры
mShader.linkCamera(xСamera, yCamera, zCamera);
//передаем в шейдерный объект координаты источника света
mShader.linkLightSource(xLightPosition, yLightPosition, zLightPosition);
//вычисляем координаты вершин
getVertex();
//вычисляем координаты нормалей
getNormal();
//очищаем кадр
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT
| GLES20.GL_DEPTH_BUFFER_BIT);
//рисуем поверхность
GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, sizeindex,
GLES20.GL_UNSIGNED_SHORT, indexBuffer);
}//конец метода
//------------------------------------------------------------------------------------------
}//конец класса
//------------------------------------------------------------------------------------------
Запустим код на исполнение и получим следующую картинку:
Теперь заставим изображение двигаться. Изобразим волну и заставим ее двигаться. Поднимем вверх источник света, чтобы на волне не было очень темных областей:

xLightPosition=5f;
yLightPosition=30f;
zLightPosition=5f;
и в методе getVertex заменим вычисление функции Y:

// определим фактор времени

double time=System.currentTimeMillis();
// заполним массив Y значениями функции
for (int j=0; j<=jmax; j++){
for (int i=0; i<=imax; i++){
y[j][i]=0.2f*(float)Math.cos(0.005*time+5*(z[j]+x[i]));
}
}
По экрану побежит волна:


Полный код урока можно скачать отсюда PolygonCell.zip

 







Дата добавления: 2015-10-19; просмотров: 509. Нарушение авторских прав; Мы поможем в написании вашей работы!



Аальтернативная стоимость. Кривая производственных возможностей В экономике Буридании есть 100 ед. труда с производительностью 4 м ткани или 2 кг мяса...

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

Расчетные и графические задания Равновесный объем - это объем, определяемый равенством спроса и предложения...

Кардиналистский и ординалистский подходы Кардиналистский (количественный подход) к анализу полезности основан на представлении о возможности измерения различных благ в условных единицах полезности...

Индекс гингивита (PMA) (Schour, Massler, 1948) Для оценки тяжести гингивита (а в последующем и ре­гистрации динамики процесса) используют папиллярно-маргинально-альвеолярный индекс (РМА)...

Методика исследования периферических лимфатических узлов. Исследование периферических лимфатических узлов производится с помощью осмотра и пальпации...

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

Дренирование желчных протоков Показаниями к дренированию желчных протоков являются декомпрессия на фоне внутрипротоковой гипертензии, интраоперационная холангиография, контроль за динамикой восстановления пассажа желчи в 12-перстную кишку...

Деятельность сестер милосердия общин Красного Креста ярко проявилась в период Тритоны – интервалы, в которых содержится три тона. К тритонам относятся увеличенная кварта (ув.4) и уменьшенная квинта (ум.5). Их можно построить на ступенях натурального и гармонического мажора и минора.  ...

Понятие о синдроме нарушения бронхиальной проходимости и его клинические проявления Синдром нарушения бронхиальной проходимости (бронхообструктивный синдром) – это патологическое состояние...

Studopedia.info - Студопедия - 2014-2024 год . (0.012 сек.) русская версия | украинская версия