5 апреля 2012 г.

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){
super.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();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
} // конец класса
------------------------------------------------------------------------------------------------------------ 
В результате получим две вращающиеся пирамиды



5 комментариев:

  1. Вы грузите текстуры в методе onSurfaceCreated. Однако если текстур будет много, экран будет черный достаточно продолжительное время. Возможно ли организовать загрузку текстур в потоке, в это время показывая что то типа "идет загрузка"?

    ОтветитьУдалить
  2. Грузятся долго не текстуры, а долго объекты bitmap. Можно объявить bitmap статическим полем в классе MyClassRenderer, например так:
    public static Bitmap bitmap1;

    Далее в конструкторе класса MyClassRenderer создаем объект bitmap1 из картинки:
    bitmap1 = bitmapFactory.decodeResource(context.getResources(),R.drawable.picture1);
    При этом статический объект bitmap1 создается в памяти один раз и удерживается в ней до выхода из программы.
    А в методе onSurfaceCreated мы будем просто копировать bitmap в память видеокарты, т.е. создавать текстурный объект.
    В классе Texture создаем дополнительный конструктор, который грузит текстуру из объекта bitmap:
    // конструктор двумерной текстуры из битмапа
    public Texture(Bitmap bitmap){
    name=0;
    names = new int[1];
    GLES20.glGenTextures(1, names, 0);
    name = names[0];
    GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, name);
    // устанавливаем параметры текстуры
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_LINEAR_MIPMAP_LINEAR);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_REPEAT);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_REPEAT);
    //переписываем bitmap в текстуру
    GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
    //создаем мипмапы
    GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
    }
    Вывод-нужно заранее создавать битмапы. А текстура создается из bitmap быстро - 20-30 мсек.



    ОтветитьУдалить
  3. Анонимный5 мая 2013 г., 10:46

    Спасибо за уроки. Только у меня две проблемы:
    1. Я написал программу, которая работает на симуляторе, но на самом телефоне работать отказывается.
    2. Мне не удается наложить две текстуры на два прямоугольника. Когда накладываю на один из них, все работает нормально.

    ОтветитьУдалить
    Ответы
    1. Анонимный6 мая 2013 г., 22:51

      Класс Texture практически как у Вас, разве что выделил функцию SetTexture, чтобы можно было менять текстуру на лету.

      public class Texture
      {
      private int name;

      public Texture (GL10 gl)
      {
      int[] names = new int [1];
      gl.glGenTextures (1, names, 0);
      name = names [0];
      }

      public void SetTexture (GL10 gl, Context context, Bitmap bitmap)
      {
      gl.glPixelStorei (GL10.GL_UNPACK_ALIGNMENT, 1);
      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);
      GLUtils.texImage2D (GL10.GL_TEXTURE_2D, 0, bitmap, 0);
      }

      public int getName ()
      {
      return name;
      }
      }

      Рендерер:

      public void onDrawFrame (GL10 gl)
      {
      gl.glClear (GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
      gl.glMatrixMode (GL10.GL_MODELVIEW);
      gl.glLoadIdentity ();
      gl.glTranslatef (0, 0, -1.2f);
      background.draw (gl, true);
      door.draw (gl, true);
      }

      Тут второй параметр у последних функций - надо ли отображать текстуру. Если один из них false, текстура второго прямоугольника отображается.

      Ну и функция рисования

      public void draw (GL10 gl, boolean tex)
      {
      vertexBuffer.position (0);
      texcoordBuffer.position (0);

      gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
      gl.glVertexPointer (3, GL10.GL_FLOAT, 0, vertexBuffer);

      if (tex && textureName != 0)
      {
      gl.glEnable (GL10.GL_TEXTURE_2D);
      gl.glBindTexture (GL10.GL_TEXTURE_2D, textureName);
      gl.glEnableClientState (GL10.GL_TEXTURE_COORD_ARRAY);
      gl.glTexCoordPointer (2, GL10.GL_FLOAT, 0, texcoordBuffer);
      }
      else
      {
      gl.glDisable (GL10.GL_TEXTURE_2D);
      }
      gl.glDrawArrays (GL10.GL_TRIANGLE_FAN, 0, 6);
      }

      Могу выслать весь проект, скажите только куда (я что-то не нашел). Мой ящик alexcov2000@mail.ru

      Удалить