OpenGL ES 1. Текстурирование на примере
В данном примере показано как создать текстурированный многогранник в Android OpenGL ES на примере пирамид. Применен объектно-ориентированный подход. Пирамида разбита на отдельные самостоятельные объекты (полигоны) - треугольники и прямоугольник, внутри которых содержатся собственные свойства полигонов - координаты вершин и текстур, а также определен метод позволяющий вращать полигоны в пространстве. Используемые классы: · Texture - класс текстуры. При создании объекта данного класса в память загружется графический файл, соответствующий данной текстуре, а также присваивается уникальное имя для обращения к текстуре. Таким образом, текстура однозначно связывается с картинкой. · Triangle - класс треугольника. Знает координаты своих вершин и координаты текстур, присвоенных вершинам. При помощи метода rotate можно поворачивать треугольник вокруг любой оси. При этом собственные координаты вершин будут меняться. К треугольнику можно привязать текстуру методом setTextureName, а также отвязать её если сделать имя текстуры равной нулю. Для правильной сортировки полигонов при рисовании полупрозрачной пирамиды в треугольник встроены координаты центра, которые также изменяются при вращении треугольника. Содержит метод draw, рисующий треугольник на экране. · Quadr - прямоугольник для дна пирамиды. Описывается аналогично треугольнику. При создании прямоугольника задаются координаты трёх вершин, а четвертая рассчитывается автоматически как сумма двух векторов образованных тремя вершинами, т.к. все четыре вершины должны лежать в одной плоскости. · Pyramid - пирамида, собранная из четырех треугольников и одного прямоугольника. Все пять полигонов жестко связаны по координатам вершин. Определены методы вращающие пирамиду относительно осей, соединяющих её центр и вершины. Каждая грань пирамиды может быть залита отдельной текстурой, а также текстура может быть общей для всех граней пирамиды. Для этого перегружен метод setTextureName. ------------------------------------------------------------------------------------------------------------ // конструктор // команда glBindTexture устанавливает текстуру с номером name public int getName(){ ------------------------------------------------------------------------------------------------------------ В нашем примере будем рисовать текстурированную пирамиду, поэтому нам понадобятся два класса треугольник для боковых граней пирамиды и прямоугольник для основания. Создадим класс Triangle, описывающий треугольник: ------------------------------------------------------------------------------------------------------------ package com.blogspot.andmonahov.pyramid; import java.nio.*; import javax.microedition.khronos.opengles.GL10; import android.opengl.Matrix; private float x1,y1,z1; // координаты второй точки треугольника private float x2,y2,z2; // координаты третьей точки треугольника private float x3,y3,z3; // в одной плоскости достаточно задать координаты одного вектора нормали для всех трех точек //для сортировки треугольников по расстоянию до камеры
public Triangle(float x1, float y1, float z1, float x2, // против часовой стрелки присваиваем координаты точкам 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()); // пересчет нормалей и центра, заполнение буферов координат и нормалей 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, // включаем использование массивов текстур gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); // указываем программе, что буфер с именем texcoordBuffer является буфером текстур // первый параметр - это количество координат (две- s и t) gl.glTexCoordPointer(2,GL10.GL_FLOAT,0,texcoordBuffer); // если имя текстуры пустое отключаем использование двумерных текстур gl.glDisable(GL10.GL_TEXTURE_2D); } // рисуем треугольник, последний параметр - это количество точек треугольника (т.е. три) gl.glDrawArrays(GL10.GL_TRIANGLES,0,3); } ------------------------------------------------------------------------------------------------------------ Таким образом мы создали класс, описывающий треугольник в трехмерном пространстве, научили его поворачиваться на произвольный угол относительно любой оси и хранить внутри себя ссылку на имя текстуры. Аналогично создается класс прямоугольника Quadr. ------------------------------------------------------------------------------------------------------------
public float getx2(){ return x2;} public float getx3(){ return x3;} public float getx4(){ return x4;} public float getxcenter(){ return xcenter;} ------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------ Рисование осуществляется в классе, реализующем интерфейс 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 Pyramid p1, p2; //конструктор // запомним контекст,он нам понадобится для загрузки текстур 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); 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, gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, //рисуем дальнюю пирамиду 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(), // грани дальней пирамиды закрасим одинаковой текстурой p2.setTextureName(tex1.getName()); } ------------------------------------------------------------------------------------------------------------ Рендерер будет создан и запущен в классе, расширяющем GLSurfaceView. Создадим такой класс MyClassSurfaceView. ------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------ Нам осталось только создать экземпляр класса MyClassSurfaceView в нашем Activity и установить вызов его через метод setContentView. ------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------ В результате получим две вращающиеся пирамиды
|