首先申明下,本文为笔者学习《OpenGL ES应用开发实践指南》的笔记,并加入笔者自己的理解和归纳总结。
天空盒是表达三维全景的一个方法,不管你的头转向哪边,这个全景图在任何方向上都能被看见。
1、立方体贴图
立方体一共有6个面,需要6个图片资源,按照左、右、下、上、前和后的顺序传递立方体的面。2、加载图片
(1) 加载图像int[] cubeResources = new int[]{
R.drawable.left, R.drawable.right,
R.drawable.bottom, R.drawable.top,
R.drawable.front, R.drawable.back
};
final int[] textureObjectIds = new int[1];
GLES20.glGenTextures(1, textureObjectIds, 0);
if (textureObjectIds[0] == 0) {
return 0;
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
final Bitmap[] cubeBitmaps = new Bitmap[6];
for (int i = 0; i < 6; i++) {
cubeBitmaps[i] = BitmapFactory.decodeResource(context.getResources(),
cubeResources[i], options);
if (cubeBitmaps[i] == null) {
GLES20.glDeleteTextures(1, textureObjectIds, 0);
return 0;
}
}
(2) 关联贴图
// 配置纹理过滤器
GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, textureObjectIds[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
// left/right
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, cubeBitmaps[0], 0);
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, cubeBitmaps[1], 0);
// bottom/top
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, cubeBitmaps[2], 0);
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, cubeBitmaps[3], 0);
// front/back
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, cubeBitmaps[4], 0);
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, cubeBitmaps[5], 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, 0);
for (Bitmap bitmap : cubeBitmaps) {
bitmap.recycle();
}
3、创建立方体
一个立方体有8个顶点,6个面,需要12个三角形来绘制这个立方体。private final static float[] vertexData = new float[]{
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, -1, -1,
-1, -1, 1,
};
三角形
indexArray = ByteBuffer.allocate(3 * 2 * 6).put(new byte[]{
// Front
0, 3, 7,
0, 7, 4,
// Back
1, 2, 6,
1, 6, 5,
// Left
2, 3, 7,
2, 7, 6,
// Right
0, 4, 5,
0, 5, 1,
// Top
0, 1, 2,
0, 2, 3,
// Bottom
4, 5, 6,
4, 6, 7
});
4、着色器文件
(1) 顶点着色器, skybox_vertex_shader.glsl文件uniform mat4 u_Matrix;
attribute vec3 a_Position;
varying vec3 v_Position;
void main()
{
v_Position = a_Position;
v_Position.z = -v_Position.z;
gl_Position = u_Matrix * vec4(a_Position, 1.0f);
gl_Position = gl_Position.xyww;
}
(2) 片段着色器,skybox_fragment_shader.glsl文件
precision mediump float;
uniform samplerCube u_textureUnit;
varying vec3 v_Position;
void main()
{
gl_FragColor = textureCube(u_textureUnit, v_Position);
}
5、绘制着色器
(1) SkyBox类public class SkyBox extends Resource {
private final static int POSITION_COMPONENT_COUNT = 3;
private final ByteBuffer indexArray;
private final static float[] vertexData = new float[]{
1, 1, 1,
1, 1, -1,
-1, 1, -1,
-1, 1, 1,
1, -1, 1,
1, -1, -1,
-1, -1, -1,
-1, -1, 1,
};
public SkyBox() {
super(vertexData);
indexArray = ByteBuffer.allocate(3 * 2 * 6).put(new byte[]{
// Front
0, 3, 7,
0, 7, 4,
// Back
1, 2, 6,
1, 6, 5,
// Left
2, 3, 7,
2, 7, 6,
// Right
0, 4, 5,
0, 5, 1,
// Top
0, 1, 2,
0, 2, 3,
// Bottom
4, 5, 6,
4, 6, 7
});
indexArray.position(0);
}
public void bindData(SkyBoxProgram program) {
bindData(program.getPositionLocation(), 0, POSITION_COMPONENT_COUNT, 0);
}
@Override
public void draw() {
GLES20.glDrawElements(GLES20.GL_TRIANGLES, 36, GLES20.GL_UNSIGNED_BYTE, indexArray);
}
}
(2) SkyBoxProgram类
public class SkyBoxProgram extends Program {
private final static String U_MATRIX = "u_Matrix";
private final static String A_POSITION = "a_Position";
private final static String U_TEXTURE_UNIT = "u_textureUnit";
private int uMatrixLocation, aPositionLocation, uTextureUnitLocation;
private int mTextureObjectId;
public SkyBoxProgram(Context context) {
super(context, R.raw.skybox_vertex_shader, R.raw.skybox_fragment_shader);
uMatrixLocation = GLES20.glGetUniformLocation(mProgramId, U_MATRIX);
aPositionLocation = GLES20.glGetAttribLocation(mProgramId, A_POSITION);
uTextureUnitLocation = GLES20.glGetAttribLocation(mProgramId, U_TEXTURE_UNIT);
mTextureObjectId = loadCubeMap(context, new int[]{
R.drawable.left, R.drawable.right,
R.drawable.bottom, R.drawable.top,
R.drawable.front, R.drawable.back
});
}
@Override
public void setUniform(float[] projectionMatrix) {
GLES20.glUseProgram(mProgramId);
GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, mTextureObjectId);
GLES20.glUniform1i(uTextureUnitLocation, 0);
}
private int loadCubeMap(Context context, int[] cubeResources) {
final int[] textureObjectIds = new int[1];
GLES20.glGenTextures(1, textureObjectIds, 0);
if (textureObjectIds[0] == 0) {
return 0;
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
final Bitmap[] cubeBitmaps = new Bitmap[6];
for (int i = 0; i < 6; i++) {
cubeBitmaps[i] = BitmapFactory.decodeResource(context.getResources(),
cubeResources[i], options);
if (cubeBitmaps[i] == null) {
GLES20.glDeleteTextures(1, textureObjectIds, 0);
return 0;
}
}
// 配置纹理过滤器
GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, textureObjectIds[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
// left/right
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, cubeBitmaps[0], 0);
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, cubeBitmaps[1], 0);
// bottom/top
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, cubeBitmaps[2], 0);
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, cubeBitmaps[3], 0);
// front/back
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, cubeBitmaps[4], 0);
GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, cubeBitmaps[5], 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, 0);
for (Bitmap bitmap : cubeBitmaps) {
bitmap.recycle();
}
return textureObjectIds[0];
}
public int getPositionLocation() {
return aPositionLocation;
}
}
(3) OpenGLSkyBoxShaderRender类
class OpenGLSkyBoxShaderRender implements GLSurfaceView.Renderer {
private float[] projectionMatrix = new float[16];
private SkyBox mSkyBox;
private SkyBoxProgram mSkyBoxProgram;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mSkyBox = new SkyBox();
mSkyBoxProgram = new SkyBoxProgram(OpenGLSkyBoxShaderActivity.this);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
Matrix.perspectiveM(projectionMatrix, 0, 45, (float)width / (float)height, 1, 10);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
mSkyBoxProgram.setUniform(projectionMatrix);
mSkyBox.bindData(mSkyBoxProgram);
mSkyBox.draw();
}
}
6、围绕场景旋转
(1) 添加OnTouchListenermSurfaceView.setOnTouchListener(new View.OnTouchListener() {
float previousX, previousY;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event != null) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
previousX = event.getX();
previousY = event.getY();
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
final float deltaX = event.getX() - previousX;
final float deltaY = event.getY() - previousY;
previousX = event.getX();
previousY = event.getY();
mSurfaceView.queueEvent(new Runnable() {
@Override
public void run() {
mSkyBoxShaderRender.handleTouchDrag(deltaX, deltaY);
}
});
}
return true;
}
return false;
}
});
(2) OpenGLSkyBoxShaderRender类
class OpenGLSkyBoxShaderRender implements GLSurfaceView.Renderer {
private float[] projectionMatrix = new float[16];
private float[] modelMatrix = new float[16];
private float[] modelProjectionMatrix = new float[16];
private SkyBox mSkyBox;
private SkyBoxProgram mSkyBoxProgram;
private float xRotation, yRotation;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mSkyBox = new SkyBox();
mSkyBoxProgram = new SkyBoxProgram(OpenGLSkyBoxShaderActivity.this);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
Matrix.perspectiveM(projectionMatrix, 0, 45, (float)width / (float)height, 1, 10);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
Matrix.setIdentityM(modelMatrix, 0);
Matrix.rotateM(modelMatrix, 0, -yRotation, 1, 0, 0);
Matrix.rotateM(modelMatrix, 0, -xRotation, 0, 1, 0);
Matrix.multiplyMM(modelProjectionMatrix, 0, projectionMatrix, 0, modelMatrix, 0);
mSkyBoxProgram.setUniform(modelProjectionMatrix);
mSkyBox.bindData(mSkyBoxProgram);
mSkyBox.draw();
}
private void handleTouchDrag(float deltaX, float deltaY) {
xRotation += deltaX / 16f;
yRotation += deltaY / 16f;
if (yRotation < -90) {
yRotation = -90;
} else if (yRotation > 90){
yRotation = 90;
}
}
}