OpenGL之基础
OpenGL之绘制简单形状
OpenGL之颜色
OpenGL之调整屏幕宽高比
OpenGL之三维
OpenGL之纹理
OpenGL之构建简单物体
OpenGL之触控反馈
OpenGL之粒子
创建天空盒
天空盒是表达三维全景的一个方法,不管你的头转向哪边,这个全景在任何方向上都能被看见。渲染天空盒最经典的方法之一是使用一个环绕观察者的立方体,并在其每个面上都附上一个细致的纹理,这也称为立方体贴图
加载立方体贴图
/**
* 加载立方贴图,天空盒
*
* @param context
* @param resIds 左、右、下、上、前、后
* @return
*/
public static int loadCubeTexture(Context context, int[] resIds) {
// 获取图像原始bitmap数据
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
Bitmap[] bitmaps = new Bitmap[resIds.length];
for (int i = 0; i < resIds.length; i++) {
bitmaps[i] = BitmapFactory.decodeResource(context.getResources(), resIds[i], options);
if (bitmaps[i] == null) {
Log.d(TAG, "loadTexture: decodeResource failed!");
return 0;
}
}
// 生成纹理
int textureIds[] = new int[1];
glGenTextures(textureIds.length, textureIds, 0);
// 绑定,后面的OpenGL调用应用于这个纹理
glBindTexture(GL_TEXTURE_CUBE_MAP, textureIds[0]);
//设置纹理过滤参数,缩小和放大
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载位图数据到OpenGL
//左
GLUtils.texImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, bitmaps[0], 0);
//右
GLUtils.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, bitmaps[1], 0);
//下
GLUtils.texImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, bitmaps[2], 0);
//上
GLUtils.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, bitmaps[3], 0);
//前
GLUtils.texImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, bitmaps[4], 0);
//后
GLUtils.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, bitmaps[5], 0);
// 回收bitmap
for (Bitmap bitmap : bitmaps) {
bitmap.recycle();
}
//解绑
glBindTexture(GL_TEXTURE_2D, 0);
return textureIds[0];
}
创建立方体
public class SkyBox {
private VertexArray mVertexArray;
private ByteBuffer mIndexBuffer;
// 顶点数据数组
private static final float[] VERTEX_DATA = new float[]{
-1f, 1f, 1f, //0左上前
1f, 1f, 1f, //1右上前
-1f, -1f, 1f, //2左下前
1f, -1f, 1f, //3右下前
-1f, 1f, -1f, //4左上后
1f, 1f, -1f, //5右上后
-1f, -1f, -1f, //6左下后
1f, -1f, -1f //7右下后
};
// 顶点数据索引,这个索引数组使用索引偏移值指向每个顶点,比如,0指向顶点数组中的第一个顶点,1指向第二个顶点
// 通过这个索引数组,把所有顶点分别绑定成三角形组,每个三角形组,包含2个三角形,代表立方体上的某一面
private static final byte[] INDEX = new byte[]{
//front
1, 3, 0,
0, 3, 2,
//back
4, 6, 5,
5, 6, 7,
//left
0, 2, 4,
4, 2, 6,
//right
5, 7, 1,
1, 7, 3,
//top
5, 1, 4,
4, 1, 0,
//bottom
6, 2, 7,
7, 2, 3
};
public SkyBox() {
mVertexArray = new VertexArray(VERTEX_DATA);
mIndexBuffer = ByteBuffer.allocateDirect(6 * 2 * 3).put(INDEX);
mIndexBuffer.position(0);
}
public void bindData(SkyProgram skyProgram) {
mVertexArray.setVertexAttribPointer(
0,
skyProgram.getAPosition(),
Constants.POSITION_COMPONENT_COUNT,
0
);
}
/**
* 绘制,glDrawElements告诉OpenGL绘制我们在bindData()中绑定的顶点
* 并且要使用mIndexBuffer所定义的索引数组,并把这个数组解释为无符号字节数
*/
public void draw() {
GLES20.glDrawElements(GLES20.GL_TRIANGLES, 36, GLES20.GL_UNSIGNED_BYTE, mIndexBuffer);
}
}
天空盒着色器
顶点着色器
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.0);
gl_Position=gl_Position.xyww;
}
先把顶点的位置传递给片段着色器,接着反转其z分量,这个传递给片段着色器的位置就是立方体上每个面之间将被插值的位置,可以使用这个位置查看天空盒的纹理上正确的部分。其z分量被反转了,使得我们可以把世界的右手坐标空间转换为天空盒所期望的左手坐标空间
通过用a_Position乘以矩阵把那个位置投影到剪裁空间坐标之后,要用下面的代码把其z分量设置为与其w分量相等的值:
gl_Position=gl_Position.xyww;
确保天空盒的每一部分都将位于归一化设备坐标的远平面上以及场景中的其他一切后面。这个技巧能够奏效,是因为透视除法把一切都除以w,并且w除以它自己,结果等于1。透视除法之后,z最终就在值为1的远平面上了
片段着色器
precision mediump float;
varying vec3 v_Position;
uniform samplerCube u_TextureUnit;
void main(){
gl_FragColor=textureCube(u_TextureUnit, v_Position);
}
为了使用立方体纹理绘制这个天空盒,我们调用textureCube()时,把那个被插值的立方体面的位置作为那个片段的纹理坐标
封装着色器程序
public class SkyProgram extends BaseProgram {
private final int mUMatrix;
private final int mUTextureUnit;
private final int mAPosition;
public SkyProgram(Context context) {
super(context, R.raw.particles8_sky_vertex, R.raw.particles8_sky_fragment);
mUMatrix = glGetUniformLocation(mProgram, U_MATRIX);
mUTextureUnit = glGetUniformLocation(mProgram, U_TEXTURE_UNIT);
mAPosition = glGetAttribLocation(mProgram, A_POSITION);
}
public void setUniforms(float[] matrix, int textureId) {
glUniformMatrix4fv(mUMatrix, 1, false, matrix, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureId);
glUniform1i(mUTextureUnit, 0);
}
public int getAPosition() {
return mAPosition;
}
}
修改渲染器,加入天空盒
在onSurfaceCreated方法中,创建天空盒相关对象
mSkyProgram = new SkyProgram(mContext);
mSkyBox = new SkyBox();
mCubeTextureId = TextureHelper.loadCubeTexture(mContext, SKY_ID);
在onDrawFrame方法中,绘制天空盒,因为使用了天空盒,不能把平移矩阵应用到天空盒上。出于这个原因,我们需要为天空盒和粒子使用一个不同的矩阵
// 天可盒使用的模型视图投影矩阵
setIdentityM(modelMatrix, 0);
multiplyMM(modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, modelMatrix, 0);
mSkyProgram.useProgram();
mSkyProgram.setUniforms(modelViewProjectionMatrix, mCubeTextureId);
mSkyBox.bindData(mSkyProgram);
mSkyBox.draw();
通过拖拽旋转天空盒
在Activity中将拖拽事件传给渲染器处理
/**
* 处理天空盒拖拽旋转,将拖拽的距离传给渲染器处理
*
* @param myRenderer
*/
private void dealSkyDrag(com.test.opengl.particles8.MyRenderer myRenderer) {
glSurfaceView.setOnTouchListener(new View.OnTouchListener() {
// 记录前一刻手指的位置
float previousX;
float 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) {
// 计算拖拽的距离
float deltaX = event.getX() - previousX;
float deltaY = event.getY() - previousY;
// 更新前一刻手指的位置
previousX = event.getX();
previousY = event.getY();
glSurfaceView.queueEvent(new Runnable() {
@Override
public void run() {
// 将拖拽的距离传给渲染器处理
myRenderer.handleDrag(deltaX, deltaY);
}
});
}
return true;
}
return false;
}
});
}
在渲染器中,通过用户在每个方向上拖动的距离,把它加到xRotation和yRotation 上,这两个变量表示以度为单位的旋转,不想让触控过于灵敏,因此用16缩减了拖动的效果,也不想上下旋转的角度过大,因此把y旋转限制在+90度和-90度之间
/**
* 处理拖拽事件
*
* @param deltaX
* @param deltaY
*/
public void handleDrag(float deltaX, float deltaY) {
xRotate += deltaX / 16f;
yRotate += deltaY / 16f;
if (yRotate > 90) {
yRotate = 90;
}
if (yRotate < -90) {
yRotate = -90;
}
updateViewProjectionMatrix();
}
/**
* 更新视图矩阵
*/
private void updateViewProjectionMatrix() {
setIdentityM(viewMatrix, 0);
Matrix.rotateM(viewMatrix, 0, -xRotate, 0f, 1f, 0f);
Matrix.rotateM(viewMatrix, 0, -yRotate, 1f, 0f, 0f);
multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
}