之前文章:
《OpenGL从入门到放弃01 》一些基本概念
《OpenGL从入门到放弃02 》GLSurfaceView和Renderer
上一篇文章我们看到三角形形状跟预期有点不一样
这是OpenGL坐标系的问题,对坐标系不明白可以看第一节,这一节我们通过投影和相机视图来解决这个问题。
先来个概念,之后再上代码
相机
生活中我们拍照,你站的高度,拿相机的位置,姿势不同,拍出来的照片也就不一样
在Android OpenGLES程序中,我们可以通过以下方法来进行相机设置:
Matrix.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上的分量
相机位置:相机的位置是比较好理解的,就是相机在3D空间里面的坐标点,
相机观察方向:相机的观察方向,对应center向量,表示的是相机镜头的朝向,你可以朝前拍、朝后拍、也可以朝左朝右,或者其他的方向。
相机UP方向:相机的UP方向,可以理解为相机顶端指向的方向。比如你把相机斜着拿着,拍出来的照片就是斜着的,你倒着拿着,拍出来的就是倒着的。
如图:center的X,Y,Z表示相机的正方向的方向向量,up的X,Y,Z表示相机的上方向的方向向量。因为空间中想要确定一个具体朝向,必须要有两组向量,所以此函数需要一个点和两组向量来确定相机的位置和具体朝向。
投影
使用OpenGl绘制的3D图形,需要展示在移动端2D设备上,这就需要投影。
OpenGl ES中有两种投影方式:一种是正交投影,一种是透视投影:
正交投影
投影物体的大小不会随观察点的远近而发生变化,我们可以使用下面方法来执行正交投影
Matrix.orthoM (float[] m, //接收正交投影的变换矩阵
int mOffset, //变换矩阵的起始位置(偏移量)
float left, //相对观察点近面的左边距
float right, //相对观察点近面的右边距
float bottom, //相对观察点近面的下边距
float top, //相对观察点近面的上边距
float near, //相对观察点近面距离
float far) //相对观察点远面距离
透视投影
随观察点的距离变化而变化,观察点越远,视图越小,反之越大,我们可以通过如下方法来设置透视投影:
Matrix.frustumM (float[] m, //接收透视投影的变换矩阵
int mOffset, //变换矩阵的起始位置(偏移量)
float left, //相对观察点近面的左边距
float right, //相对观察点近面的右边距
float bottom, //相对观察点近面的下边距
float top, //相对观察点近面的上边距
float near, //相对观察点近面距离
float far) //相对观察点远面距离
只有绘制在近平面和远平面之间的内容,才能被用户看到
转换矩阵(变换矩阵)
转换矩阵用来做什么的呢?
是否记得上面我们绘制的图形坐标需要转换为OpenGl中能处理的小端字节序(LittleEdian)
没错,转换矩阵就是用来将数据转为OpenGl ES可用的数据字节,我们将相机视图矩阵和投影矩阵相乘,便得到一个转换矩阵,然后我们再将此矩阵传给顶点着色器,具体使用方法及参数说明如下:
Matrix.multiplyMM (float[] result, //接收相乘结果
int resultOffset, //接收矩阵的起始位置(偏移量)
float[] lhs, //左矩阵
int lhsOffset, //左矩阵的起始位置(偏移量)
float[] rhs, //右矩阵
int rhsOffset) //右矩阵的起始位置(偏移量)
下面简单讲解下如何使用投影和相机视图来实现矩阵变换并传递给顶点着色器
实战
1、定义一个投影矩阵、相机矩阵、变换矩阵
private final float[] mMVPMatrix = new float[16]; //变换矩阵(投影矩阵*相机矩阵的结果,最终要传递给顶点着色器)
private final float[] mProjectionMatrix = new float[16]; //投影矩阵
private final float[] mViewMatrix = new float[16]; //相机位置矩阵
2、onSurfaceChanged 中进行设置
public void onSurfaceChanged(GL10 gl, int width, int height) {
// 设置绘图的窗口(可以理解成在画布上划出一块区域来画图)
GLES20.glViewport(0,0,width,height);
/**投影和相机视图相关**/
float ratio = (float) width / height;
//设置正交投影
// Matrix.orthoM (mProjectionMatrix, 0, -ratio, ratio, -1, 2, 3, 7);
Matrix.orthoM (mProjectionMatrix, 0,0,width, height, 0, -width, width);//这个投影会跟屏幕坐标关联上
//设置透视投影(观察点越远,视图越小),这个投影矩阵被应用于对象坐标在onDrawFrame()方法中
// Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 2, 3, 7);
//设置相机位置
Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7f, 0f, 0f, 0f, 0f, 1.0f, 1.0f);
//计算变换矩阵,最终mMVPMatrix要传递给顶点着色器
Matrix.multiplyMM(mMVPMatrix,0,mProjectionMatrix,0,mViewMatrix,0);
}
至此就得到了一个变换矩阵 mMVPMatrix,最终需要传递给顶点着色器
3、修改三角形代码
在上一节代码的基础上修改
1、修改顶点着色器
// 顶点着色器的脚本
String vertexShaderCode =
"uniform mat4 uMVPMatrix;" + //接收传入的转换矩阵
" attribute vec4 vPosition;" + //接收传入的顶点
" void main() {" +
" gl_Position = uMVPMatrix * vPosition;" + //矩阵变换计算之后的位置
" }";
2、增加变量
//变换矩阵句柄
private int mMVPMatrixHandle;
//变换矩阵,提供set方法
private float[] mvpMatrix = new float[16];
public void setMvpMatrix(float[] mvpMatrix) {
this.mvpMatrix = mvpMatrix;
}
3、修改draw方法
draw方法主要是将我们的数据传递给着色器,既然顶点着色器增加了矩阵变量,那么我们需要把变换矩阵传递给顶点着色器
// 获取变换矩阵的句柄
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// 将投影和视图转换传递给着色器,可以理解为给顶点着色器中uMVPMatrix这个变量赋值为mvpMatrix
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
4、在onDrawFrame中绘制
mGlTriangle02.setMvpMatrix(mMVPMatrix);
mGlTriangle02.draw();
mGlTriangle02在onSurfaceCreated中new出来,然后就可以看到效果了
看起来差不多了,可以看到顶点位置不变的情况下,使用投影跟相机视图确实能改变2d效果。
最后贴下三角形的全部代码
public class GLTriangle02{
// 顶点着色器的脚本
String vertexShaderCode =
"uniform mat4 uMVPMatrix;" + //接收传入的转换矩阵
" attribute vec4 vPosition;" + //接收传入的顶点
" void main() {" +
" gl_Position = uMVPMatrix * vPosition;" + //矩阵变换计算之后的位置
" }";
// 片元着色器的脚本
String fragmentShaderCode =
" precision mediump float;" + // 声明float类型的精度为中等(精度越高越耗资源)
" uniform vec4 vColor;" + // 接收传入的颜色
" void main() {" +
" gl_FragColor = vColor;" + // 给此片元的填充色
" }";
private FloatBuffer vertexBuffer; //顶点坐标数据要转化成FloatBuffer格式
// 数组中每3个值作为一个坐标点
static final int COORDS_PER_VERTEX = 3;
//三角形的坐标数组
static float triangleCoords[] = {
0.0f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
};
//顶点个数,计算得出
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
//一个顶点有3个float,一个float是4个字节,所以一个顶点要12字节
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per mVertex
//三角形的颜色数组,rgba
private float[] mColor = {
0.0f, 1.0f, 0.0f, 1.0f,
};
//当前绘制的顶点位置句柄
private int vPositionHandle;
//片元着色器颜色句柄
private int vColorHandle;
//变换矩阵句柄
private int mMVPMatrixHandle;
//这个可以理解为一个OpenGL程序句柄
private final int mProgram;
//变换矩阵,提供set方法
private float[] mvpMatrix = new float[16];
public void setMvpMatrix(float[] mvpMatrix) {
this.mvpMatrix = mvpMatrix;
}
public GLTriangle02() {
/** 1、数据转换,顶点坐标数据float类型转换成OpenGL格式FloatBuffer,int和short同理*/
vertexBuffer = GLUtil.floatArray2FloatBuffer(triangleCoords);
/** 2、加载编译顶点着色器和片元着色器*/
int vertexShader = GLUtil.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = GLUtil.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
/** 3、创建空的OpenGL ES程序,并把着色器添加进去*/
mProgram = GLES20.glCreateProgram();
// 添加顶点着色器到程序中
GLES20.glAttachShader(mProgram, vertexShader);
// 添加片段着色器到程序中
GLES20.glAttachShader(mProgram, fragmentShader);
/** 4、链接程序*/
GLES20.glLinkProgram(mProgram);
}
public void draw() {
// 将程序添加到OpenGL ES环境
GLES20.glUseProgram(mProgram);
/***1.获取句柄*/
// 获取顶点着色器的位置的句柄(这里可以理解为当前绘制的顶点位置)
vPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 获取片段着色器的vColor句柄
vColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 获取变换矩阵的句柄
mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
/**2.设置数据*/
// 启用顶点属性,最后对应禁用
GLES20.glEnableVertexAttribArray(vPositionHandle);
//准备三角形坐标数据
GLES20.glVertexAttribPointer(vPositionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 设置绘制三角形的颜色,给vColor 这个变量赋值
GLES20.glUniform4fv(vColorHandle, 1, mColor, 0);
// 将投影和视图转换传递给着色器,可以理解为给uMVPMatrix这个变量赋值为mvpMatrix
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
/** 3.绘制三角形,三个顶点*/
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用顶点数组(好像不禁用也没啥问题)
GLES20.glDisableVertexAttribArray(vPositionHandle);
}
}
到这里我们对绘制三角形的步骤已经有一个基本的了解,那么如何画一个长方形呢?其实跟画三角形差不多,下一节我们就学习一下如何绘制一个长方形,敬请期待。