
- •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. Шейдер преломления света
1.6 OpenGl es 1. Текстурирование на примере
В данном примере показано как создать текстурированный многогранник в Android OpenGL ES на примере пирамид. Применен объектно-ориентированный подход. Пирамида разбита на отдельные самостоятельные объекты (полигоны) - треугольники и прямоугольник, внутри которых содержатся собственные свойства полигонов - координаты вершин и текстур, а также определен метод позволяющий вращать полигоны в пространстве.
Используемые классы:
Texture - класс текстуры. При создании объекта данного класса в память загружется графический файл, соответствующий данной текстуре, а также присваивается уникальное имя для обращения к текстуре. Таким образом, текстура однозначно связывается с картинкой.
Triangle - класс треугольника. Знает координаты своих вершин и координаты текстур, присвоенных вершинам. При помощи метода rotate можно поворачивать треугольник вокруг любой оси. При этом собственные координаты вершин будут меняться. К треугольнику можно привязать текстуру методом setTextureName, а также отвязать её если сделать имя текстуры равной нулю. Для правильной сортировки полигонов при рисовании полупрозрачной пирамиды в треугольник встроены координаты центра, которые также изменяются при вращении треугольника. Содержит метод draw, рисующий треугольник на экране.
Quadr - прямоугольник для дна пирамиды. Описывается аналогично треугольнику. При создании прямоугольника задаются координаты трёх вершин, а четвертая рассчитывается автоматически как сумма двух векторов образованных тремя вершинами, т.к. все четыре вершины должны лежать в одной плоскости.
Pyramid - пирамида, собранная из четырех треугольников и одного прямоугольника. Все пять полигонов жестко связаны по координатам вершин. Определены методы вращающие пирамиду относительно осей, соединяющих её центр и вершины. Каждая грань пирамиды может быть залита отдельной текстурой, а также текстура может быть общей для всех граней пирамиды. Для этого перегружен метод setTextureName.
------------------------------------------------------------------------------------------------------------ Перейдем от теории к практике. Создадим класс Texture, описывающий двумерную текстуру ------------------------------------------------------------------------------------------------------------ package com.blogspot.andmonahov.pyramid; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.opengl.GLUtils; public class Texture{ private int name; // поле для хранения имени текстуры // конструктор public Texture(GL10 gl, Context context, int idpicture){ int[] names=new int[1]; // генерирует массив свободных имен текстур, состоящий из одного элемента gl.glGenTextures(1, names, 0); // запомним имя текстуры name=names[0]; // имя текстуры - это ее уникальный номер, по которому можно к ней обращаться // имя текстуры будем хранить в поле name для получения имени из других классов //позже напишем метод getName // установим выравнивание gl.glPixelStorei(GL10.GL_UNPACK_ALIGNMENT,1);
// команда glBindTexture устанавливает текстуру с номером name // в качестве текущей активной текстуры gl.glBindTexture(GL10.GL_TEXTURE_2D, name); // в дальнейшем все настройки действуют на текущую активную текстуру // устанавливаем параметры текстуры // включаем автоматическую генерацию мип-мапов всех уровней gl.glTexParameterx(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE); // устанавливаем фильтры gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR_MIPMAP_LINEAR); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); // если текстура не полностью закрывает рисуемый полигон // устанавливаем режим повтора рисунка текстуры gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT); //загружаем картинку с идентификаторм idpicture // idpicture - это уникальный номер картинки в R.java Bitmap bitmap = BitmapFactory.decodeResource (context.getResources(), idpicture); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); // удаляем картину из памяти bitmap.recycle(); // т.о. текущая текстура с именем name связана с картинкой idpicture }
public int getName(){ return name; } }
------------------------------------------------------------------------------------------------------------
В нашем примере будем рисовать текстурированную пирамиду, поэтому нам понадобятся два класса треугольник для боковых граней пирамиды и прямоугольник для основания. Создадим класс Triangle, описывающий треугольник:
------------------------------------------------------------------------------------------------------------
package com.blogspot.andmonahov.pyramid;
import java.nio.*;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.Matrix; public class Triangle { // определение полей класса координаты первой точки треугольника
private float x1,y1,z1;
// координаты второй точки треугольника
private float x2,y2,z2;
// координаты третьей точки треугольника
private float x3,y3,z3; // координаты вектора нормали т.к. три точки треугольника всегда лежат
// в одной плоскости достаточно задать координаты одного вектора нормали для всех трех точек private float nx,ny,nz; // координаты центра треугольника они будут нам нужны в дальнейшем
//для сортировки треугольников по расстоянию до камеры private float xcenter,ycenter,zcenter; // OpenGl не принимает координаты в виде чисел float // Внешние данные должны быть переданы в OpenGL в байтовых буферах // поэтому определим три буфера буфер координат вершин private FloatBuffer vertexBuffer; // буфер вектора нормали private FloatBuffer normalBuffer; // буфер координат текстур private FloatBuffer texcoordBuffer; // треугольник может быть заполнен какой либо текстурой // поэтому создадим поле для хранения имени текстуры //внутри класса треугольника private int textureName;
// конструктор
public Triangle(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3){ // треугольник однозначно описывается тремя точками поэтому в момент его создания в конструктор должны //быть переданы координаты точек порядок точек 1,2,3 должен соответствовать обходу треугольника
// против часовой стрелки присваиваем координаты точкам
this.x1=x1;
this.y1=y1;
this.z1=z1;
this.x2=x2;
this.y2=y2;
this.z2=z2;
this.x3=x3;
this.y3=y3;
this.z3=z3;
// подготавливаем буфер вершин каждая вершина треугольника содержит три коодинаты x,y,z типа float
// каждая координата типа float занимает 4 байта, итого 12 байт на вершину три вершины используют 3*12=36 байт
ByteBuffer b1 = ByteBuffer.allocateDirect(36);
b1.order(ByteOrder.nativeOrder());
vertexBuffer = b1.asFloatBuffer();
// подготавливаем буфер нормалей
ByteBuffer b2 = ByteBuffer.allocateDirect(36);
b2.order(ByteOrder.nativeOrder()); normalBuffer = b2.asFloatBuffer();
// пересчет нормалей и центра, заполнение буферов координат и нормалей
recount(); //этот метод описан ниже
// подготавливаем буфер координат текстур каждой вершине треугольника присвоены две координаты текстур s и t
// каждая координата занимает 4 байта как число типа float итого 8 байт на вершину, 24 байта не треугольник
ByteBuffer b4 = ByteBuffer.allocateDirect(24);
b4.order(ByteOrder.nativeOrder());
texcoordBuffer = b4.asFloatBuffer();
// заполняем буфер текстур выполняем обход вершин против часовой стрелки
// текстурные координаты верхней точки
texcoordBuffer.put(0.5f); // s
texcoordBuffer.put(1); // t
// текстурные коодинаты левой нижней точки
texcoordBuffer.put(0); // s
texcoordBuffer.put(0); // t
// текстурные координаты правой нижней точки
texcoordBuffer.put(1); // s
texcoordBuffer.put(0); // t
// установим текущую позицию буфера на его начало
texcoordBuffer.position(0);
// при создании треугольника ему еще не присвоена текстура поэтому ставим
textureName = 0;
// присвоить имя текстуры можно через метод setTextureName
} // описание методов класса при изменении координат вершин треугольника
// изменяются координаты вектора нормали и координаты центра
// поэтому создадим метод который будет их пересчитывать и заполнять буферы новыми координатами
private void recount(){
//заполняем буфер вершин значением координат точек
vertexBuffer.position(0);
// для вершины 1
vertexBuffer.put(x1);
vertexBuffer.put(y1);
vertexBuffer.put(z1);
// для вершины 2
vertexBuffer.put(x2);
vertexBuffer.put(y2);
vertexBuffer.put(z2);
// для вершины 3
vertexBuffer.put(x3);
vertexBuffer.put(y3);
vertexBuffer.put(z3);
vertexBuffer.position(0);
// нормаль может быть получена путем векторного произведения двух векторов - из точки 1 в точку 2 и из точки 2 в точку 3
float dx1=x2-x1;
float dy1=y2-y1;
float dz1=z2-z1;
float dx2=x3-x2;
float dy2=y3-y2;
float dz2=z3-z2;
nx=dy1*dz2-dy2*dz1;
ny=dx2*dz1-dx1*dz2;
nz=dx1*dy2-dx2*dy1;
// приведение вектора нормали к единичной длине выполнять не будем это выполнит OpenGL
normalBuffer.position(0);
// заполняем буфер нормалей, нормаль одинакова для всех трех вершин для вершин 1,2,3 одно и тоже три раза
for (int i=1;i<4;i++){
normalBuffer.put(nx);
normalBuffer.put(ny);
normalBuffer.put(nz);
}
normalBuffer.position(0);
// вычисляем центр треугольника
xcenter=(x1+x2+x3)/3;
ycenter=(y1+y2+y3)/3;
zcenter=(z1+z2+z3)/3;
}
// метод, устанавливающий для использования имя текстуры
public void setTextureName(int name){
textureName=name;
}
// нам могут понадобиться координаты вершин и центра треугольника
// поэтому опишем методы, возвращающие их
public float getx1(){ return x1;}
public float gety1(){ return y1;}
public float getz1(){ return z1;}
public float getx2(){ return x2;}
public float gety2(){ return y2;}
public float getz2(){ return z2;}
public float getx3(){ return x3;}
public float gety3(){ return y3;}
public float getz3(){ return z3;}
public float getxcenter(){ return xcenter;}
public float getycenter(){ return ycenter;}
public float getzcenter(){ return zcenter;}
// создадим метод, который поворачивает треугольник
// относительно вектора заданного двумя точками xa,ya,za и xb,yb,zb на угол angle против часовой стрелки
public void rotate(float angle,
float xa, float ya, float za,
float xb, float yb, float zb){
// создаем матрицу вращения
float [] rotatematrix=new float[16];
// передаем в метод setRotateM угол и три координаты вектора, образованного двумя точками a-b
Matrix.setRotateM(rotatematrix, 0, angle, xb-xa, yb-ya, zb-za);
// в результате получаем заполненную матрицу вращения rotatematrix
// для нахождения новых координат точек после поворота будем использовать метод multiplyMV
// умножения матрицы на вектор размером в 4 элемента зададим вектор, соединяющий точку a и вершину 1 треугольника
float [] oldvector1={x1-xa,y1-ya,z1-za,1};
// получаем новый вектор после поворота
float [] newvector=new float [4];
Matrix.multiplyMV(newvector,0,rotatematrix,0,oldvector1,0);
// добавляем к полученному вектору координаты точки a в результате получаем новые координаты точки 1
x1=newvector[0]+xa;
y1=newvector[1]+ya;
z1=newvector[2]+za;
// аналогично получаем коодинаты точек 2 и 3 после поворота поворачиваем точку 2
float [] oldvector2={x2-xa,y2-ya,z2-za,1};
Matrix.multiplyMV(newvector, 0, rotatematrix, 0, oldvector2, 0);
x2=newvector[0]+xa;
y2=newvector[1]+ya;
z2=newvector[2]+za;
// поворачиваем точку 3
float [] oldvector3={x3-xa,y3-ya,z3-za,1};
Matrix.multiplyMV(newvector, 0, rotatematrix, 0, oldvector3, 0);
x3=newvector[0]+xa;
y3=newvector[1]+ya;
z3=newvector[2]+za;
// пересчитывам нормали, центр и заполняем буферы
recount();
}
// метод, рисующий треугольник на экране
public void draw (GL10 gl){
vertexBuffer.position(0);
normalBuffer.position(0);
texcoordBuffer.position(0);
// включаем использование массивов вершин
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// указываем, что буфер с именем vertexBuffer является буфером вершин
gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);
// включаем использование массивов нормалей
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
// указываем, что буфер с именем normalBuffer является буфером нормалей
gl.glNormalPointer(GL10.GL_FLOAT,0,normalBuffer);
if (textureName!=0){
// если имя текстуры не пустое включаем использование двумерных текстур
gl.glEnable(GL10.GL_TEXTURE_2D);
// устанавливаем активной текстуру с именем textureName
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureName);
// для учета освещения при использовании текстур включаем режим модуляции
// если вместо GL10.GL_MODULATE поставить GL10.GL_REPLACE эффект освещения исчезнет
gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE);
// включаем использование массивов текстур
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// указываем программе, что буфер с именем texcoordBuffer является буфером текстур
// первый параметр - это количество координат (две- s и t)
gl.glTexCoordPointer(2,GL10.GL_FLOAT,0,texcoordBuffer); }else{
// если имя текстуры пустое отключаем использование двумерных текстур
gl.glDisable(GL10.GL_TEXTURE_2D);
}
// рисуем треугольник, последний параметр - это количество точек треугольника (т.е. три)
gl.glDrawArrays(GL10.GL_TRIANGLES,0,3);
} } // конец класса
------------------------------------------------------------------------------------------------------------
Таким образом мы создали класс, описывающий треугольник в трехмерном пространстве, научили его поворачиваться на произвольный угол относительно любой оси и хранить внутри себя ссылку на имя текстуры. Аналогично создается класс прямоугольника Quadr.
------------------------------------------------------------------------------------------------------------ package com.blogspot.andmonahov.pyramid; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; import android.opengl.Matrix;
public class Quadr { private float x1,y1,z1,x2,y2,z2,x3,y3,z3,x4,y4,z4; private float nx,ny,nz; private float xcenter,ycenter,zcenter; private FloatBuffer vertexBuffer,normalBuffer,texcoordBuffer; private int textureName; public Quadr(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3){ // присваиваем координаты трем точкам, четвертую найдем т.к. все четыре точки должны лежать в одной плоскости this.x1=x1; this.y1=y1; this.z1=z1; this.x2=x2; this.y2=y2; this.z2=z2; this.x3=x3; this.y3=y3; this.z3=z3; // подготавливаем буфер вершин ByteBuffer b1 = ByteBuffer.allocateDirect(4*12); b1.order(ByteOrder.nativeOrder()); vertexBuffer = b1.asFloatBuffer(); // подготавливаем буфер нормалей ByteBuffer b2 = ByteBuffer.allocateDirect(4*12); b2.order(ByteOrder.nativeOrder()); normalBuffer = b2.asFloatBuffer(); recount(); // пересчет нормалей и центра, поиск 4-ой точки подготавливаем и заполняем буфер координат текстур ByteBuffer b4 = ByteBuffer.allocateDirect(4*8); b4.order(ByteOrder.nativeOrder()); texcoordBuffer = b4.asFloatBuffer(); texcoordBuffer.put(0); texcoordBuffer.put(1); texcoordBuffer.put(0); texcoordBuffer.put(0); texcoordBuffer.put(1); texcoordBuffer.put(0); texcoordBuffer.put(1); texcoordBuffer.put(1); texcoordBuffer.position(0); } // метод перезаписывает буфер вершин и буфер нормалей, вычисляет центр прямоугольника private void recount(){ // ищем четвертую точку x4=x1-x2+x3; y4=y1-y2+y3; z4=z1-z2+z3; // заполняем буфер вершин значением координат точек vertexBuffer.position(0); // для вершины 1 vertexBuffer.put(x1); vertexBuffer.put(y1); vertexBuffer.put(z1); // для вершины 2 vertexBuffer.put(x2); vertexBuffer.put(y2); vertexBuffer.put(z2); // для вершины 3 vertexBuffer.put(x3); vertexBuffer.put(y3); vertexBuffer.put(z3); // для вершины 4 vertexBuffer.put(x4); vertexBuffer.put(y4); vertexBuffer.put(z4); vertexBuffer.position(0); // вычисляем нормаль float dx1=x2-x1; float dy1=y2-y1; float dz1=z2-z1; float dx2=x3-x2; float dy2=y3-y2; float dz2=z3-z2; nx=dy1*dz2-dy2*dz1; ny=dx2*dz1-dx1*dz2; nz=dx1*dy2-dx2*dy1; /заполняем буфер нормалей, нормаль одинакова для всех четырех вершин normalBuffer.position(0); // для вершин 1,2,3,4 одно и тоже 4 раза for (int i=1;i<5;i++){ normalBuffer.put(nx); normalBuffer.put(ny); normalBuffer.put(nz); } normalBuffer.position(0); // вычисляем центр xcenter=(x1+x2+x3+x4)/4; ycenter=(y1+y2+y3+y4)/4; zcenter=(z1+z2+z3+z4)/4; } // методы возвращающие координаты вершин и центра public float getx1(){ return x1;} public float gety1(){ return y1;} public float getz1(){ return z1;} public float getx2(){ return x2;} public float gety2(){ return y2;} public float getz2(){ return z2;} public float getx3(){ return x3;} public float gety3(){ return y3;} public float getz3(){ return z3;} public float getx4(){ return x4;} public float gety4(){ return y4;} public float getz4(){ return z4;} public float getxcenter(){ return xcenter;} public float getycenter(){ return ycenter;} public float getzcenter(){ return zcenter;} // метод, поворачивающий прямоугольник public void rotate(float angle, float xa, float ya, float za, float xb, float yb, float zb){ // создаем матрицу вращения float [] rotatematrix=new float[16]; // передаем в метод setRotateM угол и три координаты вектора, образованного двумя точками a-b Matrix.setRotateM(rotatematrix, 0, angle, xb-xa, yb-ya, zb-za); // в результате получаем заполненную матрицу вращения rotatematrix для нахождения новых координат точек после поворота // будем использовать метод multiplyMV умножения матрицы на вектор размером в 4 элемента // зададим вектор, соединяющий точку a и вершину 1 float [] oldvector1={x1-xa,y1-ya,z1-za,1}; // получаем новый вектор после поворота float [] newvector=new float [4]; Matrix.multiplyMV(newvector,0,rotatematrix,0,oldvector1,0); // добавляем к полученному вектору координаты точки a в результате получаем новые координаты точки 1 x1=newvector[0]+xa; y1=newvector[1]+ya; z1=newvector[2]+za; // аналогично получаем коодинаты точек 2 и 3 после поворота поворачиваем точку 2 float [] oldvector2={x2-xa,y2-ya,z2-za,1}; Matrix.multiplyMV(newvector, 0, rotatematrix, 0, oldvector2, 0); x2=newvector[0]+xa; y2=newvector[1]+ya; z2=newvector[2]+za; // поворачиваем точку 3 float [] oldvector3={x3-xa,y3-ya,z3-za,1}; Matrix.multiplyMV(newvector, 0, rotatematrix, 0, oldvector3, 0); x3=newvector[0]+xa; y3=newvector[1]+ya; z3=newvector[2]+za; // новая точка 4 ищется методом recount пересчитываем нормали, центр и заполняем буферы recount(); } // метод, устанавливающий для использования имя текстуры public void setTextureName(int name){ textureName=name; } //метод, рисующий прямоугольник public void draw (GL10 gl){ vertexBuffer.position(0); normalBuffer.position(0); texcoordBuffer.position(0); // включаем использование массивов вершин gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // указываем программе, что буфер с именем vertexBuffer является буфером вершин gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer); // включаем использование массивов нормалей gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); // указываем программе, что буфер с именем normalBuffer является буфером нормалей gl.glNormalPointer(GL10.GL_FLOAT,0,normalBuffer); if (textureName!=0){ gl.glEnable(GL10.GL_TEXTURE_2D); // устанавливаем активной текстуру с именем textureName gl.glBindTexture(GL10.GL_TEXTURE_2D, textureName); // для учета освещения при использовании текстур включаем режим модуляции gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE); // включаем использование массивов текстур gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); // указываем программе, что буфер с именем texcoordBuffer является буфером текстур gl.glTexCoordPointer(2,GL10.GL_FLOAT,0,texcoordBuffer); } else{ gl.glDisable(GL10.GL_TEXTURE_2D); } //рисуем прямоугольник gl.glDrawArrays(GL10.GL_TRIANGLE_FAN,0,4); } } // конец класса
------------------------------------------------------------------------------------------------------------ Соберем из треугольников и прямоугольника пирамиду ------------------------------------------------------------------------------------------------------------ package com.blogspot.andmonahov.pyramid; import javax.microedition.khronos.opengles.GL10; public class Pyramid { private float xcenter; private float ycenter; private float zcenter; public Triangle [] triangle=new Triangle[4]; // массив боковых треугольников public Quadr quadr; // квадрат дна // конструктор public Pyramid (float x, float y, float z, float l, float h ) { //x,y,z координаты центра основания //l пол-ширины основания //h высота //создаем боковые треугольники triangle[0]=new Triangle(x,y+h,z, x-l,y,z+l, x+l,y,z+l); triangle[1]=new Triangle(x,y+h,z, x+l,y,z+l, x+l,y,z-l); triangle[2]=new Triangle(x,y+h,z, x+l,y,z-l, x-l,y,z-l); triangle[3]=new Triangle(x,y+h,z, x-l,y,z-l, x-l,y,z+l); //создаем основание пирамиды quadr=new Quadr(x-l,y,z+l, x-l,y,z-l, x+l,y,z-l); recount(); // пересчитаем центр } // пересчет координат центра public void recount(){ xcenter=(triangle[0].getx1()+triangle[0].getx2()+triangle[0].getx3()+ triangle[2].getx2()+triangle[2].getx3())/5; ycenter=(triangle[0].gety1()+triangle[0].gety2()+triangle[0].gety3()+ triangle[2].gety2()+triangle[2].gety3())/5; zcenter=(triangle[0].getz1()+triangle[0].getz2()+triangle[0].getz3()+ triangle[2].getz2()+triangle[2].getz3())/5; } // для того, чтобы повернуть пирамиду относительно вектора a-b // на угол angle достаточно повернуть боковые треугольники и квадрат дна public void rotate(float angle, float xa, float ya, float za, float xb, float yb, float zb){ // вызываем методы вращения треугольников четырех треугольников for (int i=0; i<4; i++){ triangle[i].rotate(angle, xa, ya, za, xb, yb, zb); } // вызываем метод вращения квадрата дна quadr.rotate(angle, xa, ya, za, xb, yb, zb); // пересчитываем центр пирамиды recount(); } // частные случаи метода rotate поворачивает пирамиду относительно оси центр-вершина public void rotatecenter0 (float angle){ rotate(angle, xcenter, ycenter, zcenter, triangle[0].getx1(),triangle[0].gety1(), triangle[0].getz1()); } // аналогично получаем методы поворачивающие пирамиду относительно оси центр-нижний угол public void rotatecenter1 (float angle){ rotate(angle, xcenter, ycenter, zcenter, triangle[0].getx2(), triangle[0].gety2(), triangle[0].getz2()); } public void rotatecenter2 (float angle){ rotate(angle, xcenter, ycenter, zcenter, triangle[0].getx3(), triangle[0].gety3(), triangle[0].getz3()); } public void rotatecenter3 (float angle){ rotate(angle, xcenter, ycenter, zcenter, triangle[2].getx2(), triangle[2].gety2(), triangle[2].getz2()); } public void rotatecenter4 (float angle){ rotate(angle, xcenter, ycenter, zcenter, triangle[2].getx3(), triangle[2].gety3(), triangle[2].getz3()); } // если грани пирамиды надо закрасить разными текстурами применяем этот метод public void setTextureName(int name1, int name2, int name3, int name4, int name5){ // устанавливаем текстуру боковых треугольников triangle[0].setTextureName(name1); triangle[1].setTextureName(name2); triangle[2].setTextureName(name3); triangle[3].setTextureName(name4); // устанавливаем текстуру дна quadr.setTextureName(name5); } // если всю пирамиду надо закрасить одной текстурой применяем этот метод public void setTextureName(int name){ // устанавливаем текстуру боковых треугольников triangle[0].setTextureName(name); triangle[1].setTextureName(name); triangle[2].setTextureName(name); triangle[3].setTextureName(name); // устанавливаем текстуру дна quadr.setTextureName(name); } // рисуем пирамиду public void draw (GL10 gl){ // рисуем боковые треугольники for (int i=0; i<4; i++){ triangle[i].draw(gl); } // рисуем дно пирамиды quadr.draw(gl); } }//конец класса
------------------------------------------------------------------------------------------------------------
Рисование осуществляется в классе, реализующем интерфейс GLSurfaceView.Renderer. Создадим такой класс MyClassRenderer. Предварительно поместим в каталоги res\drawable-hdpi, res\drawable-ldpi, res\drawable-mdpi какие-нибудь картинки с именами icon1.jpg, icon2.jpg, icon3.jpg, icon4.jpg, icon5.jpg. Эти фалы будут загружаться в качестве текстур.
------------------------------------------------------------------------------------------------------------
package com.blogspot.andmonahov.pyramid;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
public class MyClassRenderer implements GLSurfaceView.Renderer{
// интерфейс GLSurfaceView.Renderer содержит три метода onDrawFrame, onSurfaceChanged, onSurfaceCreated
// которые должны быть переопределены текущий контекст
private Context context;
// позиция камеры (почему поля определены как public static поясню позже)
public static float xposition,yposition,zposition;
// направление взгляда камеры
private float xlook,ylook,zlook;
// координаты вектора, указывающего камере, где верх
private float xtop,ytop,ztop;
// массивы для хранения цветов материала цвета общего фонового освещения
private float [] ambientmaterialArray={0.2f, 0.2f, 0.2f, 1f};
// цвета отраженного рассеянного света
private float [] diffusematerialArray={0.8f, 0.8f, 0.8f, 1f};
// цвета отраженного зеркального света
private float [] specularmaterialArray={0.5f, 0.5f, 0.5f,1f};
// соответствующие им буферы цветов материала
private FloatBuffer ambientmaterialBuffer,
diffusematerialBuffer,specularmaterialBuffer;
// массив для хранения координат источника света
private float [] positionlightArray={0.5f,0,0.2f,0};
// массивы для хранения цветов источника света цвета общего фонового освещения
private float [] ambientlightArray={0.5f, 0.5f, 0.5f, 1f};
// цвета отраженного рассеянного света
private float [] diffuselightArray={0.8f, 0.8f, 0.8f, 1f};
// цвета отраженного зеркального света
private float [] specularlightArray={0.8f, 0.8f, 0.8f,1f};
// соответствующие им буферы источника света
private FloatBuffer positionlightBuffer,ambientlightBuffer,
diffuselightBuffer,specularlightBuffer;
//текстуры private Texture tex1,tex2,tex3,tex4,tex5;
//пирамиды
private Pyramid p1, p2;
//конструктор public MyClassRenderer(Context context) {
// запомним контекст,он нам понадобится для загрузки текстур
this.context=context;
// настройки камеры
xposition=0.3f;
yposition=0.3f;
zposition=1.2f;
xlook=0;
ylook=0;
zlook=0;
xtop=0;
ytop=1;
ztop=0;
// переписываем цвета материалов из массивов в буферы
ByteBuffer b1 = ByteBuffer.allocateDirect(4 * 4);
b1.order(ByteOrder.nativeOrder());
ambientmaterialBuffer = b1.asFloatBuffer();
ambientmaterialBuffer.put(ambientmaterialArray);
ambientmaterialBuffer.position(0);
//
ByteBuffer b2 = ByteBuffer.allocateDirect(4 * 4);
b2.order(ByteOrder.nativeOrder());
diffusematerialBuffer = b2.asFloatBuffer();
diffusematerialBuffer.put(diffusematerialArray);
diffusematerialBuffer.position(0);
//
ByteBuffer b3 = ByteBuffer.allocateDirect(4 * 4);
b3.order(ByteOrder.nativeOrder());
specularmaterialBuffer = b3.asFloatBuffer();
specularmaterialBuffer.put(specularmaterialArray);
specularmaterialBuffer.position(0);
//
// переписываем координаты источника света в буфер
ByteBuffer b4 = ByteBuffer.allocateDirect(4 * 4);
b4.order(ByteOrder.nativeOrder());
positionlightBuffer = b4.asFloatBuffer();
positionlightBuffer.put(positionlightArray);
positionlightBuffer.position(0);
//
// переписываем цвета источника света из массивов в буферы
ByteBuffer b5 = ByteBuffer.allocateDirect(4 * 4);
b5.order(ByteOrder.nativeOrder());
ambientlightBuffer = b5.asFloatBuffer();
ambientlightBuffer.put(ambientlightArray);
ambientlightBuffer.position(0);
//
ByteBuffer b6 = ByteBuffer.allocateDirect(4 * 4);
b6.order(ByteOrder.nativeOrder());
diffuselightBuffer = b6.asFloatBuffer();
diffuselightBuffer.put(diffuselightArray);
diffuselightBuffer.position(0);
//
ByteBuffer b7 = ByteBuffer.allocateDirect(4 * 4);
b7.order(ByteOrder.nativeOrder());
specularlightBuffer = b7.asFloatBuffer();
specularlightBuffer.put(specularlightArray);
specularlightBuffer.position(0);
// создаем пирамиды
p1=new Pyramid(-0.15f, 0, 0, 0.3f, 0.45f);
p2=new Pyramid( 0.3f, 0.2f,-1, 0.3f, 0.45f);
}
public void onDrawFrame(GL10 gl) {
//этот метод вызывается циклически здесь мы будем выполнять рисование
// очищаем буферы глубины и цвета
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// перейдем в режим работы с матрицей модели-вида
gl.glMatrixMode(GL10.GL_MODELVIEW);
// сбросим матрицу модели-вида на единичную
gl.glLoadIdentity();
// поворачиваем пирамиды вокруг осей относительно текущего положения пирамиды
p1.rotatecenter0(0.5f);
p1.rotatecenter1(0.2f);
p2.rotatecenter1(-0.5f);
p2.rotatecenter2(-0.2f);
// устанавливаем камеру
GLU.gluLookAt(gl, xposition,yposition,zposition, xlook,ylook,zlook, xtop,ytop,ztop); // включаем источник света с номером 0
gl.glEnable(GL10.GL_LIGHT0);
// устанавливаем координаты источника света
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, positionlightBuffer);
// устанавливаем цвета источника света
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, ambientlightBuffer);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, diffuselightBuffer);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, specularlightBuffer);
// устанавливаем цвета материала
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, ambientmaterialBuffer);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, diffusematerialBuffer);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, specularmaterialBuffer);
//рисуем дальнюю пирамиду
p2.draw(gl);
//рисуем ближнюю пирамиду
p1.draw(gl);
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
// вызывается при изменении размеров окна установим область просмотра равной размеру экрана
gl.glViewport(0, 0, width, height);
// подсчитаем отношение ширина/высота экрана
float ratio = (float) width / height;
// перейдем в режим работы с матрицей проекции
gl.glMatrixMode(GL10.GL_PROJECTION);
// сбросим матрицу проекции на единичную
gl.glLoadIdentity();
// устанавливаем перспективную проекцию угол обзора 60 градусов
// передняя отсекающая плоскость 0.1 задняя отсекающая плоскость 100
GLU.gluPerspective (gl, 60, ratio, 0.1f, 100f);
// перейдем в режим работы с матрицей модели-вида
gl.glMatrixMode(GL10.GL_MODELVIEW);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// вызывается при создании окна включим пересчет нормалей на единичную длину
gl.glEnable(GL10.GL_NORMALIZE);
// включим сглаживание цветов
gl.glShadeModel(GL10.GL_SMOOTH);
// включим проверку глубины
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
// разрешим использовать освещение
gl.glEnable(GL10.GL_LIGHTING);
//загружаем текстуры
tex1=new Texture(gl,context, R.drawable.icon1);
tex2=new Texture(gl,context, R.drawable.icon2);
tex3=new Texture(gl,context, R.drawable.icon3);
tex4=new Texture(gl,context, R.drawable.icon4);
tex5=new Texture(gl,context, R.drawable.icon5);
//привязываем текстуры к пирамидам грани ближней пирамиды закрасим разными текстурами
p1.setTextureName(tex1.getName(),tex2.getName(), tex3.getName(),tex4.getName(),tex5.getName());
// грани дальней пирамиды закрасим одинаковой текстурой
p2.setTextureName(tex1.getName());
} }//конец класса
------------------------------------------------------------------------------------------------------------
Рендерер будет создан и запущен в классе, расширяющем GLSurfaceView. Создадим такой класс MyClassSurfaceView.
------------------------------------------------------------------------------------------------------------ package com.blogspot.andmonahov.pyramid; import android.content.Context; import android.opengl.GLSurfaceView; public class MyClassSurfaceView extends GLSurfaceView{ //создадим ссылку для хранения экземпляра нашего класса рендерера private MyClassRenderer renderer; // конструктор public MyClassSurfaceView(Context context) { // вызовем конструктор родительского класса GLSurfaceView super(context); // создадим экземпляр нашего класса MyClassRenderer renderer = new MyClassRenderer(context); // запускаем рендерер setRenderer(renderer); // установим режим циклического запуска метода onDrawFrame setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); // при этом запускается отдельный поток // в котором циклически вызывается метод onDrawFrame // т.е. бесконечно происходит перерисовка кадров } } // конец класса
------------------------------------------------------------------------------------------------------------
Нам осталось только создать экземпляр класса MyClassSurfaceView в нашем Activity и установить вызов его через метод setContentView.
------------------------------------------------------------------------------------------------------------
package
com.blogspot.andmonahov.pyramid;
import
android.app.Activity;
import
android.os.Bundle;
public
class PyramidActivity extends Activity {
// создадим ссылку
на экземпляр нашего класса
MyClassSurfaceView
private
MyClassSurfaceView mGLSurfaceView;
//
переопределим метод onCreate
@Override
public
void onCreate(Bundle
savedInstanceState){
s
uper.onCreate(savedInstanceState);
//создадим экземпляр
нашего класса MyClassSurfaceView
mGLSurfaceView
= new MyClassSurfaceView(this);
//вместо вызова
стандартного контента
setContentView(R.layout.main);
//вызовем экземпляр
нашего класса MyClassSurfaceView
setContentView(mGLSurfaceView);
// на экране появится
поверхность для рисования в OpenGL
ES
}
@Override
protected
void onPause() {
super.onPause();
mGLSurfaceView.onPause();
}
@Override
protected
void onResume() {
super.onResume();
mGLSurfaceView.onResume();
}
}
// конец класса
------------------------------------------------------------------------------------------------------------
В результате получим две вращающиеся пирамиды