OpenGL ES之一——概念扫盲
OpenGL ES之二——Android中的OpenGL ES概述
OpenGL ES之三——绘制纯色背景
OpenGL ES之四——绘制点,线,三角形
OpenGL ES之五——相机和投影,绘制等腰三角形
OpenGL ES之六——绘制矩形和圆形
OpenGL ES之七——着色器语言GLSL
OpenGL ES之八——GLES20类和Matrix类
OpenGL ES之九——相机和投影
OpenGL ES之十——纹理贴图(展示一张图片)
OpenGL ES之十一——绘制3D图形
OpenGL ES之十二——地球仪和VR图
上一篇文章中着重记录了顶点着色器和片段着色器。但是我们发现在OpenGL ES的虚拟坐标中如果我们不处理顶点位置数据,即使我们给了三个顶点可以组成等腰直角三角形,那么在手机屏幕上显示出来的也不是一个等腰直角三角形。这一篇文章就来解决它。
重点:矩阵和向量;相机和投影;利用变换矩阵绘制等腰三角形
在大学时候都学过线性代数,当时学到矩阵和向量的时候感觉非常的枯燥乏味,感觉这东西做出来有什么用。其实我们错了,数学本是因解决问题而生,只是当时没有从源头找到方法,所以就只是为了应付考试而学习。
这里只是简单的介绍,不会深入说明
。
在数学中,矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合 ,最早来自于方程组的系数及常数所构成的方阵。这一概念由19世纪英国数学家凯利首先提出。矩阵是高等代数学中的常见工具,也常见于统计分析等应用数学学科中。在物理学中,矩阵于电路学、力学、光学和量子物理中都有应用;计算机科学中,三维动画制作也需要用到矩阵。
在数学中,向量(也称为欧几里得向量、几何向量、矢量),指具有大小(magnitude)和方向的量。它可以形象化地表示为带箭头的线段。箭头所指:代表向量的方向;线段长度:代表向量的大小。
由m*n个数按照一定顺序排列成m行n列的矩形数表称为矩阵,而向量则是由n个有序数组成的数组。故矩阵中的行可以看作一个行向量,列可以看作列向量。所以可以说向量是矩阵的一部分。
在三维图形学中,一般使用的是4阶矩阵。在DirectX中使用的是行向量,如[xyzw],所以与矩阵相乘时,向量在前矩阵在后。OpenGL中使用的是列向量,如[xyzx]T,所以与矩阵相乘时,矩阵在前,向量在后,我们最终通过“变换矩阵”来得到我们想要的向量
。
这里只是介绍一个大致的概念,后面会详细的分析相机和投影。
这里的相机和平时的相机概念不是一个,这里的相机指的是观察外界的视角。我们从照相机或者摄像机的角度来观察这个世界,这就是我们这里的“相机”。相机对应于OpenGL的世界,决定相机拍摄的结果(也就是最后屏幕上展示的结果),包括相机位置、相机观察方向以及相机的UP方向。
1. 相机位置:相机的位置是比较好理解的,就是相机在3D空间里面的坐标点。
2. 相机观察方向:相机的观察方向,表示的是相机镜头的朝向,你可以朝前拍、朝后拍、也可以朝左朝右,或者其他的方向。
3. 相机UP方向:相机的UP方向,可以理解为相机顶端指向的方向。比如你把相机斜着拿着,拍出来的照片就是斜着的,你倒着拿着,拍出来的就是倒着的。
Android OpenGLES程序中,我们可以通过Matrix.setLookAtM来进行相机设置:
/**
* Defines a viewing transformation in terms of an eye point, a center of
* view, and an up vector.
*
* @param rm returns the result
* @param rmOffset index into rm where the result matrix starts
* @param eyeX eye point X
* @param eyeY eye point Y
* @param eyeZ eye point Z
* @param centerX center of view X
* @param centerY center of view Y
* @param centerZ center of view Z
* @param upX up vector X
* @param upY up vector Y
* @param upZ up vector Z
*/
public static void setLookAtM(float[] rm, //接收相机变换矩阵
int rmOffset, //变换矩阵的起始位置(偏移量)
float eyeX,float eyeY, float eyeZ, //相机位置
float centerX,float centerY,float centerZ, //观测点位置
float upX,float upY,float upZ) //up向量在xyz上的分量) {
------------省略代码-------------
}
相机视角观察到的世界最终都要变成平面2D图像展示在屏幕上,这个过程就是投影,从3D到2D的转换。Android OpenGLES的世界中,投影有两种,一种是正交投影,另外一种是透视投影
。
特点:物体呈现出来的大小不会随着其距离视点的远近而发生变化。
Android OpenGLES程序中,我们可以通过Matrix.orthoM来设置正交投影,如下
/**
* Computes an orthographic projection matrix.
*
* @param m returns the result
* @param mOffset
* @param left
* @param right
* @param bottom
* @param top
* @param near
* @param far
*/
public static void orthoM(float[] m, //接收正交投影的变换矩阵
int mOffset, //变换矩阵的起始位置(偏移量)
float left, //相对观察点近面的左边距
float right,//相对观察点近面的右边距
float bottom, //相对观察点近面的下边距
float top,//相对观察点近面的上边距
float near,//相对观察点近面距离
float far) //相对观察点远面距离{
---------------省略代码--------------
}
特点:物体离视点越远,呈现出来的越小。离视点越近,呈现出来的越大。(类似于我们的眼睛观察世界)
Android OpenGLES程序中,我们可以通过Matrix.frustumM来设置透视投影,如下
/**
* Defines a projection matrix in terms of six clip planes.
*
* @param m the float array that holds the output perspective matrix
* @param offset the offset into float array m where the perspective
* matrix data is written
* @param left
* @param right
* @param bottom
* @param top
* @param near
* @param far
*/
public static void frustumM(float[] m, //接收透视投影的变换矩阵
int mOffset, //变换矩阵的起始位置(偏移量)
float left,//相对观察点近面的左边距
float right,//相对观察点近面的右边距
float bottom, //相对观察点近面的下边距
float top, //相对观察点近面的上边距
float near, //相对观察点近面距离
float far) //相对观察点远面距离 {
---------------省略代码--------------
}
由于在OpenGL ES中顶点位置信息表示使用的是向量,如下每一行是一个顶点的位置向量(x,y,z)。想要使三角形显示为等腰三角形也就是要在虚拟坐标系中完成对各个顶点位置的变换,也就是对三个向量的变换。而想要实现对向量变换就要得到变换矩阵。
//三个顶点的位置参数
private float triangleCoords[] = {
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
注意:通过上面的相机和投影的介绍,目的只有一个就是透过相应的相机和投影的操作得到最终的“变换矩阵”
。获取变换矩阵还要将相机矩阵和投影矩阵进行操作,使用如下方法。
Matrix.multiplyMM (float[] result, //接收相乘结果
int resultOffset, //接收矩阵的起始位置(偏移量)
float[] lhs, //左矩阵
int lhsOffset, //左矩阵的起始位置(偏移量)
float[] rhs, //右矩阵
int rhsOffset) //右矩阵的起始位置(偏移量)
本文接OpenGL ES之四向下探索如何将上文中的非等腰直角三角形变为等腰直角三角形,所以使用的仍是上一篇的工程只是将其完善。没有提到的要变化的地方就是未发生变化,这里只说明变化的地方。
#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aColor;
uniform mat4 u_Matrix;
out vec4 vColor;
void main() {
gl_Position = u_Matrix*vPosition;
gl_PointSize = 10.0;
vColor = aColor;
}
//相机矩阵
private final float[] mViewMatrix = new float[16];
//投影矩阵
private final float[] mProjectMatrix = new float[16];
//最终变换矩阵
private final float[] mMVPMatrix = new float[16];
//返回属性变量的位置
//变换矩阵
private int uMatrixLocation;
//位置
private int aPositionLocation;
//颜色
private int aColorLocation;
onSurfaceCreated中添加如下代码
uMatrixLocation = GLES30.glGetUniformLocation(mProgram, "u_Matrix");
aPositionLocation = GLES30.glGetAttribLocation(mProgram, "vPosition");
aColorLocation = GLES30.glGetAttribLocation(mProgram, "aColor");
onSurfaceChanged中添加如下代码
//计算宽高比
float ratio=(float)width/height;
//设置透视投影
Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
//设置相机位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
onDrawFrame中完成最终绘制如下:
@Override
public void onDrawFrame(GL10 gl) {
//把颜色缓冲区设置为我们预设的颜色
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
//将变换矩阵传入顶点渲染器
GLES20.glUniformMatrix4fv(uMatrixLocation,1,false,mMVPMatrix,0);
//准备坐标数据
GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
//启用顶点位置句柄
GLES30.glEnableVertexAttribArray(aPositionLocation);
//准备颜色数据
GLES30.glVertexAttribPointer(aColorLocation, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
//启用顶点颜色句柄
GLES30.glEnableVertexAttribArray(aColorLocation);
//绘制三个点
//GLES30.glDrawArrays(GLES30.GL_POINTS, 0, POSITION_COMPONENT_COUNT);
//绘制三条线
//GLES30.glLineWidth(3);//设置线宽
//GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, POSITION_COMPONENT_COUNT);
//绘制三角形
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);
//禁止顶点数组的句柄
GLES30.glDisableVertexAttribArray(aPositionLocation);
GLES30.glDisableVertexAttribArray(aColorLocation);
}
其中其实变化的只是在绘制之前添加了GLES20.glUniformMatrix4fv(uMatrixLocation,1,false,mMVPMatrix,0);
。其他方法中变换的只是将以前直接写入的属性位置参数变为了更正规的一种方式。
public class SimpleShapeRender implements GLSurfaceView.Renderer {
//一个Float占用4Byte
private static final int BYTES_PER_FLOAT = 4;
//三个顶点
private static final int POSITION_COMPONENT_COUNT = 3;
//顶点位置缓存
private final FloatBuffer vertexBuffer;
//顶点颜色缓存
private final FloatBuffer colorBuffer;
//渲染程序
private int mProgram;
//相机矩阵
private final float[] mViewMatrix = new float[16];
//投影矩阵
private final float[] mProjectMatrix = new float[16];
//最终变换矩阵
private final float[] mMVPMatrix = new float[16];
//返回属性变量的位置
//变换矩阵
private int uMatrixLocation;
//位置
private int aPositionLocation;
//颜色
private int aColorLocation;
//三个顶点的位置参数
private float triangleCoords[] = {
0.5f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
//三个顶点的颜色参数
private float color[] = {
1.0f, 0.0f, 0.0f, 1.0f,// top
0.0f, 1.0f, 0.0f, 1.0f,// bottom left
0.0f, 0.0f, 1.0f, 1.0f// bottom right
};
public SimpleShapeRender() {
//顶点位置相关
//分配本地内存空间,每个浮点型占4字节空间;将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
vertexBuffer = ByteBuffer.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexBuffer.put(triangleCoords);
vertexBuffer.position(0);
//顶点颜色相关
colorBuffer = ByteBuffer.allocateDirect(color.length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
colorBuffer.put(color);
colorBuffer.position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//将背景设置为白色
GLES20.glClearColor(1.0f,1.0f,1.0f,1.0f);
//编译顶点着色程序
String vertexShaderStr = ResReadUtils.readResource(R.raw.vertex_simple_shade);
int vertexShaderId = ShaderUtils.compileVertexShader(vertexShaderStr);
//编译片段着色程序
String fragmentShaderStr = ResReadUtils.readResource(R.raw.fragment_simple_shade);
int fragmentShaderId = ShaderUtils.compileFragmentShader(fragmentShaderStr);
//连接程序
mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
//在OpenGLES环境中使用程序
GLES30.glUseProgram(mProgram);
uMatrixLocation = GLES30.glGetUniformLocation(mProgram, "u_Matrix");
aPositionLocation = GLES30.glGetAttribLocation(mProgram, "vPosition");
aColorLocation = GLES30.glGetAttribLocation(mProgram, "aColor");
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置绘制窗口
GLES30.glViewport(0, 0, width, height);
//计算宽高比
float ratio=(float)width/height;
//设置透视投影
Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
//设置相机位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
}
@Override
public void onDrawFrame(GL10 gl) {
//把颜色缓冲区设置为我们预设的颜色
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
//将变换矩阵传入顶点渲染器
GLES20.glUniformMatrix4fv(uMatrixLocation,1,false,mMVPMatrix,0);
//准备坐标数据
GLES30.glVertexAttribPointer(aPositionLocation, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
//启用顶点位置句柄
GLES30.glEnableVertexAttribArray(aPositionLocation);
//准备颜色数据
GLES30.glVertexAttribPointer(aColorLocation, 4, GLES30.GL_FLOAT, false, 0, colorBuffer);
//启用顶点颜色句柄
GLES30.glEnableVertexAttribArray(aColorLocation);
//绘制三个点
//GLES30.glDrawArrays(GLES30.GL_POINTS, 0, POSITION_COMPONENT_COUNT);
//绘制三条线
//GLES30.glLineWidth(3);//设置线宽
//GLES30.glDrawArrays(GLES30.GL_LINE_LOOP, 0, POSITION_COMPONENT_COUNT);
//绘制三角形
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);
//禁止顶点数组的句柄
GLES30.glDisableVertexAttribArray(aPositionLocation);
GLES30.glDisableVertexAttribArray(aColorLocation);
}
}
上面是利用相机和透视投影完成的变换,其实还可以只利用正交投影来完成变换。只需改动如下:
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置绘制窗口
GLES30.glViewport(0, 0, width, height);
/*//相机和透视投影方式
//计算宽高比
float ratio=(float)width/height;
//设置透视投影
Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
//设置相机位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);*/
//正交投影方式
final float aspectRatio = width > height ?
(float) width / (float) height :
(float) height / (float) width;
if (width > height) {
//横屏
Matrix.orthoM(mMVPMatrix, 0, -aspectRatio, aspectRatio, -1f, 1f, -1f, 1f);
} else {
//竖屏
Matrix.orthoM(mMVPMatrix, 0, -1f, 1f, -aspectRatio, aspectRatio, -1f, 1f);
}
}