Кубическая текстура представляет из себя шесть фотографий окружающего пространства: "Правая сторона", "Левая сторона", "Верх", "Низ", "Передняя сторона", "Задняя сторона", которые должны быть подготовлены заранее. Всем шести картинкам присваивается одно имя текстуры и обращение идет к текстуре производится по данному имени. Для создания эффекта зеркального отражения используется автоматическая генерация текстур при помощи команды glTexGeni с параметром GL_REFLECTION_MAP. При этом каждой вершине полигона присваивается координата текстуры, соответствующая зеркальному отражению от поверхности полигона.
Автоматическая генерация текстур несмотря на высокую скорость расчета текстурных координат имеет свои недостатки. В частности, при перемещении камеры зеркальное отражение перемещается в месте с ней и расплывается. Чтобы избавиться от таких нежелательных эффектов, нужно изменять матрицу текстуры. Матрица текстуры - это аналог матрицы модели-вида, применяемый не к координатам вершин X, Y, Z, а координатам текстур S, T, R. Все операции, которые можно применить к матрице модели-вида, можно применять и к матрице текстуры, но действовать они будут на текстурные координаты. По умолчанию матрица текстуры равна единичной матрице. Чтобы зеркальное отражение выглядело корректно нужно компенсировать поворот и перемещение камеры. Для этого записываем в массив текущую матрицу модели-вида с учетом все поворотов и перемещений камеры, получаем из нее обратную матрицу модели-вида, а затем зануляем у обратной матрицы модели-вида элементы, связанные с перемещением. Результат всех этих манипуляций записываем в матрицу текстуры. Перед рисованием следующего полигона матрицу текстуры нужно снова превратить в единичную.
Продемонстрирую это на примере. За основу возьмем код статьи, в которой мы рисовали полупрозрачные пирамиды. Изменим код класса Texture на следующий:
------------------------------------------------------------------------------------------------------------
package com.blogspot.andmonahov.pyramidmirror;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11ExtensionPack;
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 idpicture1,int idpicture2,int idpicture3,
int idpicture4,int idpicture5,int idpicture6){
// передаем в конструктор шесть фотографий с идентификаторами
// idpicture1, idpicture2, idpicture3, idpicture4, idpicture5, idpicture6
int[] names=new int[1];
// генерируем свободное имя текстуры
gl.glGenTextures(1, names, 0);
name=names[0];
gl.glPixelStorei(GL10.GL_UNPACK_ALIGNMENT,1);
// Вместо параметра состояния GL10.GL_TEXTURE_2D будем использовать GL11ExtensionPack.GL_TEXTURE_CUBE_MAP
gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, name);
// устанавливаем параметры текстуры
gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR_MIPMAP_LINEAR);
gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR);
gl.glTexParameterx(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
GL10.GL_TEXTURE_WRAP_S,
GL10.GL_CLAMP_TO_EDGE);
gl.glTexParameterx(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
GL10.GL_TEXTURE_WRAP_T,
GL10.GL_CLAMP_TO_EDGE);
gl.glTexParameterx(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
//загружаем картинки правая сторона (положительная координата X)
Bitmap bitmap1 = BitmapFactory.decodeResource
(context.getResources(),idpicture1);
GLUtils.texImage2D(
GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_POSITIVE_X,
0, bitmap1, 0);
bitmap1.recycle();
//левая сторона (отрицательная координата X)
Bitmap bitmap2 = BitmapFactory.decodeResource
(context.getResources(),idpicture2);
GLUtils.texImage2D(
GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
0, bitmap2, 0);
bitmap2.recycle();
//верх (положительная координата Y)
Bitmap bitmap3 = BitmapFactory.decodeResource
(context.getResources(),idpicture3);
GLUtils.texImage2D(
GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
0, bitmap3, 0);
bitmap3.recycle();
//низ (отрицательная координата Y)
Bitmap bitmap4 = BitmapFactory.decodeResource
(context.getResources(),idpicture4);
GLUtils.texImage2D(
GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
0, bitmap4, 0);
bitmap4.recycle();
//задняя часть (положительная координата Z)
Bitmap bitmap5 = BitmapFactory.decodeResource
(context.getResources(),idpicture5);
GLUtils.texImage2D(
GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
0, bitmap5, 0);
bitmap5.recycle();
//передняя часть (отрицательная координата Z)
Bitmap bitmap6 = BitmapFactory.decodeResource
(context.getResources(),idpicture6);
GLUtils.texImage2D(
GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
0, bitmap6, 0);
bitmap6.recycle();
}
// метод возвращает имя текстуры
public int getName(){
return name;
}
} //конец класса
------------------------------------------------------------------------------------------------------------
Далее нам не нужно задавать координаты текстуры для вершин полигонов, т.к. мы попросим OpenGL генерировать их автоматически с параметром GL_REFLECTION_MAP, создающим координаты зеркального отражения. В классе Triangle заменим метод draw, рисующий треугольник:
------------------------------------------------------------------------------------------------------------
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(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP);
// для учета освещения при использовании текстур включаем режим модуляции
gl.glTexEnvx(GL10.GL_TEXTURE_ENV,
GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE);
// устанавливаем активной текстуру с именем textureName
gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP,
textureName);
GL11ExtensionPack gl11ep = (GL11ExtensionPack) gl;
GL11 gl11 = (GL11) gl;
// переходим в режим работы с текстурной матрицей
gl.glMatrixMode(GL10.GL_TEXTURE);
gl.glLoadIdentity();
// записываем в массив modelViewMatrix текущую матрицу модели-вида
float[] modelViewMatrix = new float[16];
gl11.glGetFloatv(GL11.GL_MODELVIEW_MATRIX, modelViewMatrix, 0);
//записываем в массив invertModelViewMatrix обратную матрицу модели-вида
float[] invertModelViewMatrix=new float [16];
Matrix.invertM(invertModelViewMatrix,0,modelViewMatrix,0);
// зануляем у обратной матрицы модели-вида компоненты, связанные с переносом
invertModelViewMatrix[12]=0;
invertModelViewMatrix[13]=0;
invertModelViewMatrix[14]=0;
// записываем invertModelViewMatrix в матрицу текстуры
gl.glLoadMatrixf(invertModelViewMatrix,0);
//генерируем координаты зеркального отражения
gl.glEnable(GL11ExtensionPack.GL_TEXTURE_GEN_STR);
gl11ep.glTexGeni(GL11ExtensionPack.GL_TEXTURE_GEN_STR,
GL11ExtensionPack.GL_TEXTURE_GEN_MODE,
GL11ExtensionPack.GL_REFLECTION_MAP);
//переходим в режим работы с матрицей модели-вида
gl.glMatrixMode(GL10.GL_MODELVIEW);
}else{
// если имя текстуры пустое отключаем использование кубических текстур
gl.glDisable(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP);
}
// рисуем треугольник последний параметр - это количество точек треугольника (т.е. три)
gl.glDrawArrays(GL10.GL_TRIANGLES,0,3);
}
------------------------------------------------------------------------------------------------------------
Аналогично заменим метод draw в классе Quadr.
Отличие будет только в последней строке. Вместо glDrawArrays(GL10.GL_TRIANGLES,0,3) у прямоугольника будет glDrawArrays(GL10.GL_TRIANGLE_FAN,0,4);
Изменим код класса MyClassRenderer. Вместо объявления пяти текстур объявим только одну private Texture texcubemap; и выполним в методе onSurfaceCreated загрузку только одной кубической текстуры
texcubemap=new Texture(gl,context, R.drawable.icon1,R.drawable.icon2,
R.drawable.icon3,R.drawable.icon4,
R.drawable.icon5,R.drawable.icon6);
Для улучшения эффекта зеркальности увеличим Альфа-компоненту отраженного рассеянного света:
private float [] diffusematerialArray={0.8f, 0.8f, 0.8f, 0.8f};
Привожу код класса MyClassRenderer полностью:
-----------------------------------------------------------------------------------------------------------
package com.blogspot.andmonahov.pyramidmirror;
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 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, 0.8f};
// цвета отраженного зеркального света
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 texcubemap;
//пирамиды
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);
// включаем режим освещения внешних и внутренних сторон
gl.glLightModelx(GL10.GL_LIGHT_MODEL_TWO_SIDE, GL10.GL_TRUE);
// отключаем отсечение невидимых граней
gl.glDisable(GL10.GL_CULL_FACE);
// включаем альфа-компонету цвета
gl.glEnable(GL10.GL_ALPHA);
// включаем смешивание цветов
gl.glEnable(GL10.GL_BLEND);
// устанавливаем режим смешивания цветов
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
//
//загружаем фотографии в кубическую текстуру
texcubemap=new Texture(gl,context, R.drawable.icon1,R.drawable.icon2,
R.drawable.icon3,R.drawable.icon4,
R.drawable.icon5,R.drawable.icon6);
//привязываем текстуры к пирамидам
p1.setTextureName(texcubemap.getName());
p2.setTextureName(texcubemap.getName());
}
}
-------------------------------------------------------------------------------------------------------
Получаем две красивые вращающиеся пирамиды