天天看点

Opengl ES系列学习--变换

     本节我们继续学习变换一章的内容,原作者在本章内容单面讲解了大量的矩阵变换的内容,非常清晰,大家如果有兴趣研究的话,可以自己计算一下,我们就不细看了。

     先来看一下我们最终实现的效果:

Opengl ES系列学习--变换

     本小节要实现的效果相对比较简单,我们就是在上一节的基础上,加了一个变换矩阵,让我们的纹理转起来。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个单位,此时移动一个单位就只有屏幕的五分之一;缩放前如果平移一个单位,就相当于屏幕的一半了,这个大家验证一下就很容易理解了。

     好了,本节的内容就这些了,下节我们继续学习坐标系统的内容。