本节我们继续学习变换一章的内容,原作者在本章内容单面讲解了大量的矩阵变换的内容,非常清晰,大家如果有兴趣研究的话,可以自己计算一下,我们就不细看了。
先来看一下我们最终实现的效果:
本小节要实现的效果相对比较简单,我们就是在上一节的基础上,加了一个变换矩阵,让我们的纹理转起来。GlMatrixRender类的完整源码如下:
package com.opengl.learn.aric;
import android.content.Context;
import android.opengl.GLES32;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
import com.opengl.learn.OpenGLUtils;
import com.opengl.learn.R;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLES20.GL_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.GL_ELEMENT_ARRAY_BUFFER;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_STATIC_DRAW;
import static android.opengl.GLES20.GL_TEXTURE0;
import static android.opengl.GLES20.GL_TEXTURE1;
import static android.opengl.GLES20.GL_TEXTURE_2D;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.GL_UNSIGNED_SHORT;
import static android.opengl.GLES20.glGenBuffers;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glUniformMatrix4fv;
import static android.opengl.Matrix.rotateM;
public class GlMatrixRender implements GLSurfaceView.Renderer {
private final float[] mVerticesData =
{
-0.5f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
};
private final float[] mColorsData =
{
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f,
};
private final float[] mTextureData =
{
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
};
private final short[] mIndicesData =
{
0, 1, 2,
0, 2, 3,
};
private static final String TAG = GlMatrixRender.class.getSimpleName();
private static final int BYTES_PER_FLOAT = 4;
private static final int BYTES_PER_SHORT = 2;
private static final int POSITION_COMPONENT_COUNT = 3;
private static final int COLOR_COMPONENT_COUNT = 3;
private static final int TEXTURE_COMPONENT_COUNT = 2;
private static final int INDEX_COMPONENT_COUNT = 1;
private static final int MATRIX_LENGHT = 16;
private Context mContext;
private int mProgramObject;
private int uTextureContainer, containerTexture;
private int uTextureFace, faceTexture;
private int uMatrixLocation;
private FloatBuffer mVerticesBuffer;
private FloatBuffer mColorsBuffer;
private FloatBuffer mTextureBuffer;
private ShortBuffer mIndicesBuffer;
private int mWidth, mHeight;
private int mVAO, mVBO, mCBO, mTBO, mEBO;
private float[] uMatrix = new float[MATRIX_LENGHT];
private long runTime = 0;
public GlMatrixRender(Context context) {
mContext = context;
mVerticesBuffer = ByteBuffer.allocateDirect(mVerticesData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mVerticesBuffer.put(mVerticesData).position(0);
mColorsBuffer = ByteBuffer.allocateDirect(mColorsData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mColorsBuffer.put(mColorsData).position(0);
mTextureBuffer = ByteBuffer.allocateDirect(mTextureData.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
mTextureBuffer.put(mTextureData).position(0);
mIndicesBuffer = ByteBuffer.allocateDirect(mIndicesData.length * BYTES_PER_SHORT)
.order(ByteOrder.nativeOrder()).asShortBuffer();
mIndicesBuffer.put(mIndicesData).position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mProgramObject = OpenGLUtils.loadProgram(mContext, R.raw.glmatrix_vertex, R.raw.glmatrix_fragment);
uTextureContainer = glGetUniformLocation(mProgramObject, "uTextureContainer");
uTextureFace = glGetUniformLocation(mProgramObject, "uTextureFace");
uMatrixLocation = glGetUniformLocation(mProgramObject, "uMatrix");
int[] array = new int[1];
GLES32.glGenVertexArrays(1, array, 0);
mVAO = array[0];
array = new int[4];
glGenBuffers(4, array, 0);
mVBO = array[0];
mCBO = array[1];
mTBO = array[2];
mEBO = array[3];
Log.e(TAG, "onSurfaceCreated, " + mProgramObject + ", uTexture: " + uTextureContainer + ", uTextureFace: " + uTextureFace);
containerTexture = OpenGLUtils.loadTexture(mContext, R.mipmap.container);
faceTexture = OpenGLUtils.loadTexture(mContext, R.mipmap.awesomeface);
loadBufferData();
}
private void loadBufferData() {
GLES32.glBindVertexArray(mVAO);
mVerticesBuffer.position(0);
GLES32.glBindBuffer(GL_ARRAY_BUFFER, mVBO);
GLES32.glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mVerticesData.length, mVerticesBuffer, GL_STATIC_DRAW);
GLES32.glVertexAttribPointer(0, POSITION_COMPONENT_COUNT, GL_FLOAT, false, 0, 0);
GLES32.glEnableVertexAttribArray(0);
mColorsBuffer.position(0);
GLES32.glBindBuffer(GL_ARRAY_BUFFER, mCBO);
GLES32.glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mColorsData.length, mColorsBuffer, GL_STATIC_DRAW);
GLES32.glVertexAttribPointer(1, COLOR_COMPONENT_COUNT, GL_FLOAT, false, 0, 0);
GLES32.glEnableVertexAttribArray(1);
mTextureBuffer.position(0);
GLES32.glBindBuffer(GL_ARRAY_BUFFER, mTBO);
GLES32.glBufferData(GL_ARRAY_BUFFER, BYTES_PER_FLOAT * mTextureData.length, mTextureBuffer, GL_STATIC_DRAW);
GLES32.glVertexAttribPointer(2, TEXTURE_COMPONENT_COUNT, GL_FLOAT, false, 0, 0);
GLES32.glEnableVertexAttribArray(2);
mIndicesBuffer.position(0);
GLES32.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mEBO);
GLES32.glBufferData(GL_ELEMENT_ARRAY_BUFFER, BYTES_PER_SHORT * mIndicesData.length, mIndicesBuffer, GL_STATIC_DRAW);
GLES32.glUseProgram(mProgramObject);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public void onDrawFrame(GL10 gl) {
GLES32.glViewport(0, 0, mWidth, mHeight);
GLES32.glClear(GL_COLOR_BUFFER_BIT);
GLES32.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
GLES32.glEnableVertexAttribArray(0);
GLES32.glEnableVertexAttribArray(1);
GLES32.glEnableVertexAttribArray(2);
setMatrix();
GLES32.glUniformMatrix4fv(uMatrixLocation, 1, false, uMatrix, 0);
GLES32.glActiveTexture(GL_TEXTURE0);
GLES32.glBindTexture(GL_TEXTURE_2D, containerTexture);
GLES32.glUniform1i(uTextureContainer, 0);
GLES32.glActiveTexture(GL_TEXTURE1);
GLES32.glBindTexture(GL_TEXTURE_2D, faceTexture);
GLES32.glUniform1i(uTextureFace, 1);
GLES32.glBindVertexArray(mVAO);
GLES32.glDrawElements(GL_TRIANGLES, mIndicesData.length, GL_UNSIGNED_SHORT, 0);
GLES32.glDisableVertexAttribArray(0);
GLES32.glDisableVertexAttribArray(1);
GLES32.glDisableVertexAttribArray(2);
}
private void setMatrix() {
long now = System.currentTimeMillis();
if (runTime == 0) {
runTime = now;
}
float degree = (now - runTime) / 50 * 5;
Matrix.setIdentityM(uMatrix, 0);
Matrix.scaleM(uMatrix, 0, 0.5f, 0.5f, 1f);
Matrix.rotateM(uMatrix, 0, degree, 0f, 0f, 1f);
}
}
其中大部分方法和上一节完全相同,新增的矩阵变量uMatrix、时间计算变量runTime,两个变量都是在setMatrix方法中生效的,计算当前时间距离第一帧绘制的时长,得到ms数后除以50,再乘以5,也就是说每过50ms,我们让纹理旋转5度的意思。得到矩阵的计算结果后,调用GLES32.glUniformMatrix4fv(uMatrixLocation, 1, false, uMatrix, 0)把矩阵数据传给顶点着色器中的uniform mat4 uMatrix 矩阵变量,然后顶点着色器在执行时,把矩阵结果和顶点数组相乘,就把我们想要的意图应用在顶点数组上了。
Matrix.setIdentityM(uMatrix, 0);
Matrix.scaleM(uMatrix, 0, 0.5f, 0.5f, 1f);
Matrix.rotateM(uMatrix, 0, degree, 0f, 0f, 1f);
我们使用的矩阵也非常简单,先设置单位矩阵,然后对矩阵的X、Y、Z轴分别进行0.5、0.5、1倍的缩放,最后以Z轴为旋转轴旋转degree角度,连续起来就是我们最开始看到的动画了。
最后来看一下着色器的代码,片段着色器和上一节完全相同,顶点着色器源码如下:
#version 320 es
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec3 aColor;
layout(location = 2) in vec2 aTexCoord;
uniform mat4 uMatrix;
out vec3 outColor;
out vec2 outTexCoord;
void main()
{
outColor = aColor;
outTexCoord = aTexCoord;
gl_Position = vec4(aPosition, 1.0) * uMatrix;
}
我这里把是用顶点向量乘以矩阵,实际的结果就是绕Z轴以-degree角度进行旋转了,原理也没细搞,矩阵运算有点头疼。正确的应该是矩阵乘以顶点向量,这样最后的结果,就是右手坐标系(大拇指指向我们的身体表示正方向),纹理会绕Z轴正方向,以正角度旋转,这才是我们预期的结果。
void main()
{
outColor = aColor;
outTexCoord = aTexCoord;
gl_Position = uMatrix * vec4(aPosition, 1.0);
}
最后需要说一下,矩阵操作时,scaleM、rotateM这两个API的调用顺序不影响最后的结果,也就是说先缩放后旋转,或者先旋转后缩放,得到的结果都是一样的;但是如果有平移的时候就要小心了,先平移后缩放的结果,和先缩放后平移的结果是完全不一样的,大家可以自己试下,执行了缩放后,原本的屏幕宽高是一个单位,如果缩放5倍,那么此时再平移,屏幕宽高最大是5个单位,此时移动一个单位就只有屏幕的五分之一;缩放前如果平移一个单位,就相当于屏幕的一半了,这个大家验证一下就很容易理解了。
好了,本节的内容就这些了,下节我们继续学习坐标系统的内容。