本节我们来学习一下坐标系统,坐标系统应该是很重要的一节知识,是学习Opengl的过程中必不可少的一节课,如果能深刻的理解坐标系统,就能在复杂的实际工作中正确的把握住各种坐标运算。如下几张截图是原作者特别说明的,在大家的实际工作中,肯定可以作为标准,如果碰到类似坐标运算的问题了,第一时间过画对比看一看,对坐标系统的认识应该就会更进一步。
相信大家在实际工作中使用到Opengl坐标变换时,肯定会用到这些非常实用的变换说明,万丈高楼平地起,所有的知识都是用最基础的原理搭建起来的。我在实际工作中也经常碰到类似的问题,有时候看似很简单,很基础的问题,但是我们能完全正解的解答吗?估计要打个大大的问号,也正是因为这样,我们必须对用到的知识尽量深的追根溯源,完全搞明白,我们的技术功底也正是在这样一个个搞清楚的基础上提升的。
好了,来看一下本节我们实现的最终效果。
一步步正确的实现了预期的效果,感觉好兴奋,我们对Opengl的认识的正在一步步的提升,而且很明显,在实现的过程中,根本不用想,直接设置矩阵,就知道显示出来应该是什么样子,这才能说明我们完全理解它了。
第一张图片中的效果比较简单,还是一个二维平面,然后对平面进行旋转就可以得到了,在之前的章节中,我们也讲过类似的效果,可以参考:Opengl ES系列学习--序;第二张图片和第三张git动画就是我们要讲的重点了。本节我们使用的所有顶点着色器和片段着色器都是和上一节完全相同的,没有任何改变,主要的修改就是Matrix矩阵。
第一张图片的实现是在GlCoordinate2DRender类中的,不知道啥情况,github也访问不了,所有后面的章节内容都无法上传到github,而CSDN传的实在太慢,所以都没有传代码,不过Render渲染类全部都贴出来了,主要的代码也就是Render渲染类和glsl着色器了,如果有哪位朋友需要代码的话,本人免费赠送!
我们先来看一下GlCoordinate2DRender类,完整源码如下:
package com.opengl.learn.aric.coordinate;
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;
public class GlCoordinate2DRender 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 = GlCoordinate2DRender.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 float[] projectionMatrix = new float[MATRIX_LENGHT];
private float[] viewMatrix = new float[MATRIX_LENGHT];
public GlCoordinate2DRender(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() {
Matrix.setIdentityM(uMatrix, 0);
Matrix.setIdentityM(viewMatrix, 0);
Matrix.setIdentityM(projectionMatrix, 0);
float aspect = (float) mWidth / (float) mHeight;
Matrix.perspectiveM(projectionMatrix, 0, 45f, aspect, 1.0f, 100f);
Matrix.translateM(viewMatrix, 0, 0.0f, 0.0f, -3.0f);
Matrix.scaleM(viewMatrix, 0, 0.5f, 0.5f, 1f);
Matrix.rotateM(viewMatrix, 0, -60, 1f, 0f, 0f);
Matrix.multiplyMM(uMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
}
}
和上一节相比,修改很小,就是修改了一下setMatrix方法的实现。因为我们要有三维的视觉效果,所以使用透视投影。调用Matrix.perspectiveM生成透视投影矩阵,六个参数我们之前也已经讲过了,这里就不再赘述了。特别需要注意的是最后两个参数,分别表示透视投影平截头体的近平面和远平面,必须是正值,否则不会画出任何东西。这里是正值,而我们再把物体放到世界空间中的时候(也就是平移),Z轴分量又必须是负数,因为我们要移动的是物体,透视投影限制了我们的视口可以看到的部分Z轴是在(1,100)之间的,如果我们移动物体时,以正值移动,也就是移向我们眼睛,那么它就会超出透视投影的Z轴范围,所以什么东西都看不到。
调用Matrix.rotateM接口把平面绕X轴旋转-60度,使用右手坐标系统,X轴旋转的正方向就是朝向我们,负方向也就是背离我们了,这个在Opengl ES系列学习--序一节中也详细的说明过了。
还有一点也必须注意,坐标系统的这几个变换,通常情况下,我们必须先进行平移,把物体放置到世界空间中,然后再进行旋转、缩放等变换,这个差别我们上一节也有讲述过一些注意点。
剩下的就是矩阵变换了。在理解了原理的基础上,我们完全可以抛开原理,只需要处理矩阵,然后把它使用在顶点坐标上就OK了。在后面光照等章节的学习中,基本都是这样的,先学习原理,理解了原理之后,要实现效果,我们就非常直接的知道如何在片段着色器中进行数据计算就行了,完全不需要再想原理了。不过所有的计算都是基于原理模型的基础上产生的。
再来看一下git动画的实现,对应的是GlCoordinate3DRender类,该类的完整源码如下:
package com.opengl.learn.aric.coordinate;
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_DEPTH_BUFFER_BIT;
import static android.opengl.GLES20.GL_DEPTH_TEST;
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.glGenBuffers;
import static android.opengl.GLES20.glGetUniformLocation;
public class GlCoordinate3DRender implements GLSurfaceView.Renderer {
private final float[] mVerticesData =
{
// back face
0.5f, 0.5f, -0.5f, // (5) Top-right far
0.5f, -0.5f, -0.5f, // (7) Bottom-right far
-0.5f, -0.5f, -0.5f, // (6) Bottom-left far
-0.5f, -0.5f, -0.5f, // (6) Bottom-left far
-0.5f, 0.5f, -0.5f, // (4) Top-left far
0.5f, 0.5f, -0.5f, // (5) Top-right far
// front face
-0.5f, 0.5f, 0.5f, // (0) Top-left near
-0.5f, -0.5f, 0.5f, // (2) Bottom-left near
0.5f, -0.5f, 0.5f, // (3) Bottom-right near
0.5f, -0.5f, 0.5f, // (3) Bottom-right near
0.5f, 0.5f, 0.5f, // (1) Top-right near
-0.5f, 0.5f, 0.5f, // (0) Top-left near
// left face
-0.5f, 0.5f, -0.5f, // (4) Top-left far
-0.5f, -0.5f, -0.5f, // (6) Bottom-left far
-0.5f, -0.5f, 0.5f, // (2) Bottom-left near
-0.5f, -0.5f, 0.5f, // (2) Bottom-left near
-0.5f, 0.5f, 0.5f, // (0) Top-left near
-0.5f, 0.5f, -0.5f, // (4) Top-left far
// right face
0.5f, 0.5f, 0.5f, // (1) Top-right near
0.5f, -0.5f, 0.5f, // (3) Bottom-right near
0.5f, -0.5f, -0.5f, // (7) Bottom-right far
0.5f, -0.5f, -0.5f, // (7) Bottom-right far
0.5f, 0.5f, -0.5f, // (5) Top-right far
0.5f, 0.5f, 0.5f, // (1) Top-right near
// bottom face
-0.5f, -0.5f, 0.5f, // (2) Bottom-left near
-0.5f, -0.5f, -0.5f, // (6) Bottom-left far
0.5f, -0.5f, -0.5f, // (7) Bottom-right far
0.5f, -0.5f, -0.5f, // (7) Bottom-right far
0.5f, -0.5f, 0.5f, // (3) Bottom-right near
-0.5f, -0.5f, 0.5f, // (2) Bottom-left near
// top face
-0.5f, 0.5f, -0.5f, // (4) Top-left far
-0.5f, 0.5f, 0.5f, // (0) Top-left near
0.5f, 0.5f, 0.5f, // (1) Top-right near
0.5f, 0.5f, 0.5f, // (1) Top-right near
0.5f, 0.5f, -0.5f, // (5) Top-right far
-0.5f, 0.5f, -0.5f, // (4) Top-left far
};
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, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
};
private static final String TAG = GlCoordinate3DRender.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 float[] projectionMatrix = new float[MATRIX_LENGHT];
private float[] viewMatrix = new float[MATRIX_LENGHT];
private long startTime = 0;
private float[][] directions = {
{0f, 0.0f, -10f}, {2.3f, 1.7f, -15.0f}, {-2.5f, -0.2f, -20.5f}, {-0.8f, -1.65f, -12.3f}, {1.24f, -3.4f, -15.5f},
{-0.8f, 2.0f, -7.5f}, {0.97f, -2.0f, -5.5f}, {1.5f, 2.40f, -9.5f}, {3f, 0.2f, -20.5f}, {-0.93f, -2.3f, -8.5f},
};
public GlCoordinate3DRender(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);
}
@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);
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);
GLES32.glUseProgram(mProgramObject);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mWidth = width;
mHeight = height;
}
@Override
public void onDrawFrame(GL10 gl) {
if (startTime == 0) {
startTime = System.currentTimeMillis();
}
GLES32.glViewport(0, 0, mWidth, mHeight);
GLES32.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GLES32.glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
GLES32.glEnable(GL_DEPTH_TEST);
GLES32.glEnableVertexAttribArray(0);
GLES32.glEnableVertexAttribArray(1);
GLES32.glEnableVertexAttribArray(2);
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);
for (int i = 0; i < directions.length; i++) {
setMatrix(i);
GLES32.glUniformMatrix4fv(uMatrixLocation, 1, false, uMatrix, 0);
GLES32.glDrawArrays(GL_TRIANGLES, 0, mVerticesData.length);
}
GLES32.glDisableVertexAttribArray(0);
GLES32.glDisableVertexAttribArray(1);
GLES32.glDisableVertexAttribArray(2);
}
private void setMatrix(int i) {
Matrix.setIdentityM(uMatrix, 0);
Matrix.setIdentityM(viewMatrix, 0);
Matrix.setIdentityM(projectionMatrix, 0);
float aspect = (float) mWidth / (float) mHeight;
Matrix.perspectiveM(projectionMatrix, 0, 45f, aspect, 1.0f, 100f);
Matrix.translateM(viewMatrix, 0, directions[i][0], directions[i][1], directions[i][2]);
int[] rotate = {0, 0, 0};
int remaind = i % 3;
rotate[remaind] = 1;
long now = System.currentTimeMillis();
long degree = (now - startTime) / 100 * 5 * i;
Matrix.rotateM(viewMatrix, 0, degree, rotate[0], rotate[1], rotate[2]);
Matrix.multiplyMM(uMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
}
}
和平面实现差别也不大,顶点和片段着色器还是完全相同。主要的差别就是顶点坐标、纹理坐标、矩阵变换setMatrix方法,我们逐个来看一下它们的差别。
首先,我们需要绘制一个立方体,在原来二维的基础上,Z轴不能再永远为0了,必须给Z轴赋值。如下图。
我们在以后的绘图过程中,一定要养成好的习惯,顶点绘制的时候,逆时针的顺序、(012)、(230)的闭合顺序,这样即使出问题的时候也方便查,要不然,如果效果不对,我们就必须逐个去梳理一下顶点顺序,如果顶点量比较大的话,那可真是个量大的工作!(012)、(230)的顶点顺序看着就非常舒服,中间点共用,起始点和结束点共用,差别只是中间那个顶点,如下图。
在GlCoordinate3DRender类中的所有顶点我也全部按照这样的顺序排出来了,这里还涉及到一个问题,逆时针,那left face、right face、back face这些面怎样才算逆时针呢?比如上面的立方体图,我们以back face为例,如果我们这样透视看过去,那么应该是(467)、(754),然后我们要清楚,我们实际看到的物体面是对着我们的外侧面,也就是说我们要沿Y轴把立方体旋转180度之后,back face才会外侧面对着我们,那个时候的逆时针顶点顺序应该是(576)、(645),也就是我们这样透视看过去时候要反转一下才对,大家可以试一下,按照透视顺序指定顶点,画出来看笑脸的嘴巴就会是反方向的。
我是把所有的顶点顺序全部按照外侧面逆时针顺序调整过了,所以对应的纹理坐标也就完全一样了,不要忘记,上一节我们总结的:Android设备上,纹理坐标的原点在左上角,右下角的纹理坐标是(1,1)!千万不要搞错!!指定纹理坐标时,也应该按照逆时针的顺序,养成这样良好的习惯。
接着我们来看一下setMatrix方法的实现,该效果中的10个立方体的位置我也是特意调整过的,最中间没有旋转效果的是第一个立方体,我们的思路非常清晰,给每个立方体指定不同的位置和旋转角度,旋转角度和之前实现的一样,根据时间差不断变换,计算出来这两个参数,设置到Matrix中,然后把Matrix结果运用到顶点坐标上就可以了,抛开代码,如果我们的计算结果正确,那出来的结果就应该非常正确。
directions数据定义了10个立方体的平移向量,把它们放置在世界空间中的不同位置,千万记住,先平移!!剩下的degree、rotate的计算就很简单了,计算完成,GLES32.glUniformMatrix4fv(uMatrixLocation, 1, false, uMatrix, 0),把计算结果设置到顶点着色器中的uMatrix变量上。
本节的内容基本就介绍完了, 回过头我们想一下:局部空间 -----> 世界空间 -----> 观察空间 -----> 裁剪空间 -----> 屏幕空间,整个变换过程我们是怎么完成的?就是setMatrix的矩阵变换就完成了,所以后续我们就要对Matrix有更高一个档次的认识,简单的看它是在计算数组,而实际的原理,它是在一步步的变换Opengl的坐标空间!
下一节我们继续介绍摄像机的内容。