Android OpenGL天空盒

Android OpenGL天空盒

首先申明下,本文为笔者学习《OpenGL ES应用开发实践指南》的笔记,并加入笔者自己的理解和归纳总结。

天空盒是表达三维全景的一个方法,不管你的头转向哪边,这个全景图在任何方向上都能被看见。

1、立方体贴图

立方体一共有6个面,需要6个图片资源,按照左、右、下、上、前和后的顺序传递立方体的面。
立方体贴图的惯例是:在立方体内部使用左手坐标系统,而在立方体外部使用右手坐标系统。所以前面的纹理贴到z轴的负面,后面的纹理贴到z轴的正面。

Android OpenGL天空盒_第1张图片

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个三角形来绘制这个立方体。
Android OpenGL天空盒_第2张图片
顶点数组
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) 添加OnTouchListener
mSurfaceView.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;
		}

	}
}

你可能感兴趣的:(Android OpenGL天空盒)