这一节主要内容是矩阵的使用,投影和相机相关知识。
一、矩阵相关
由于OpenGL ES使用的是列向量,所以向量与矩阵计算方式为 矩阵乘向量;在DirectX中使用的是行向量计算方式为 向量乘矩阵。
二、投影相关
在OpenGL ES中投影方式有两种,分别为 正交投影和透视投影两种。
1、正交投影
Matrix.orthoM(
orthoM, //存储生成矩阵元素的float[]类型数组
0, //填充起始偏移量
left, right, //near面的left,right
bottom, top, //near面的bottom,top
near, far //near面,far面与视点的距离
);
与现实观察物体不一样。
可以把正交投影看成一个透明的长方体,叫做视景体。
视景体分为 上平面【up】、下平面【bottom】、左平面【left】、右平面【right】、近平面【near】、远平面【far】
视景体(也就是透明的长方体盒子)内物体会平行的投影在近平面,超出视景体部分物体会被剪裁掉。并且不会因为物体位置而发生和近大远小的效果。远近一样大。
近平面可以理解为离你最近的那个长方体的一个面。当然前提是你和相机是在一个位置。
2、透视投影
Matrix.frustumM(
frustumM, //存储生成矩阵元素的float[]类型数组
0, //填充起始偏移量
left, right, //near面的left,right
bottom, top, //near面的bottom,top
near, far //near面,far面与视点的距离
);
与现实世界观察物体一样,会产生近大远小的效果。
视景体类似于圆锥体,越近越大,越远越小。
三、相机
setLookAtM(
float[] rm, //存储生成矩阵元素的float[]类型数组
int rmOffset,//填充起始偏移量
float eyeX,
float eyeY,
float eyeZ, //摄像机位置X,Y,Z坐标
float centerX,
float centerY,
float centerZ, //观察目标X,Y,Z坐标
float upX,
float upY,
float upZ) //up向量在X,Y,Z上的分量
相机是观察物体的视点,相当于人的眼睛的位置。
下面改装一下我们上一节代码,看看投影效果。
修改顶点着色器代码如下
//顶点着色器代码
private final String vertex = "" +
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"void main(){" +
"gl_Position=vMatrix*vPosition;"+
"}";
这里增加了一个矩阵vMatrix定义为
uniform mat4 vMatrix;
并与顶点坐标向量相乘,我们前面说过,在OpenGL ES中使用的是列向量,所以向量和矩阵相乘时,矩阵在前向量在后。
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//计算屏幕宽高比
float ratio = (float)width/height;
//储存投影矩阵
float[] mPMatrix = new float[16];
//储存相机位置矩阵
float[] mVMatrix = new float[16];
//最终得到的矩阵
mMVPMatrix = new float[16];
//透视矩阵
//存储生成矩阵元素的float[]类型数组
//填充起始偏移量
//near面的left,right,bottom,top
//near面,far面与视点的距离
Matrix.frustumM(mPMatrix, 0, -ratio, ratio, -1,1,3, 6);
//存储生成矩阵元素的float[]类型数组
//填充起始偏移量
//摄像机位置X,Y,Z坐标
//观察目标X,Y,Z坐标
//up向量在X,Y,Z上的分量,也就是相机上方朝向,upY=1朝向手机上方,upX=1朝向手机右侧,upZ=1朝向与手机屏幕垂直
Matrix.setLookAtM(mVMatrix, 0, 0,0,6, 0,0,0,0,1,0);
//以上两个方法只能得到矩阵并不能使其生效
//下面通过矩阵计算得到最终想要的矩阵
//存放结果的总变换矩阵
//结果矩阵偏移量
//左矩阵
//左矩阵偏移量
//右矩阵
//右矩阵偏移量
Matrix.multiplyMM(mMVPMatrix, 0,mPMatrix, 0, mVMatrix, 0);
//当大小改变时重置视区大小
GLES20.glViewport(0,0, width, height);
}
然后就是透视矩阵和相机矩阵的设置,最后通过Matrix.multiplyMM();方法将两个矩阵相乘,得到最终需要的矩阵。
mMVPMatrix 是个成员变量,方便下面使用。
@Override
public void onDrawFrame(GL10 gl) {
//清空缓冲区,与 GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);对应
GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);
//使用OpenGL程序
GLES20.glUseProgram(program);
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
GLES20.glUniformMatrix4fv(vMatrix, 1 ,false, mMVPMatrix, 0);
……………………
}
在onDrawFrame方法增加两行代码
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
GLES20.glUniformMatrix4fv(vMatrix, 1 ,false, mMVPMatrix, 0);
运行一下看下效果
发现得到了一个等腰直角三角形。
红色的三角形有点难看,让我们为他增加更多的颜色。
同样是修改顶点着色器代码
如果想要查看正交投影效果只需要将frustumM修改成orthoM即可
//顶点着色器代码
private final String vertex = "" +
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"varying vec4 vColor;" +
"attribute vec4 aColor;" +
"void main(){" +
"gl_Position=vMatrix*vPosition;"+
"vColor=aColor;" +
"}";
在代码中增加
"varying vec4 vColor;" +
"attribute vec4 aColor;" +
和
"vColor=aColor;" +
varying是顶点着色器与片元着色器传递数据用的,一般顶点着色器修改varying的值,片元着色器使用varying的值。
现在顶点着色器已经提供了传递的值,那么我们需要在片元着色器内使用所以简单修改一下片元着色器代码。
//片元着色器代码
private final String fragment = "" +
"precision mediump float;" +
"varying vec4 vColor;" +
"void main(){" +
"gl_FragColor=vColor;"+
"}";
有没有发现改动呢,其实这里只改了一个位置那就是将之前的uniform vec4 vColor;改成了varying vec4 vColor;
现在着色器代码修改完成,下一步就是颜色值数据。将原来的一组颜色值修改为三组,分别是红绿蓝
//颜色值
private final float[] colors = {
1.0f,0.0f,0.0f,1.0f,
0.0f,1.0f,0.0f,1.0f,
0.0f,0.0f,1.0f,1.0f
};
在
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//将背景设置为灰色,这里只是设置,并没有立即生效
GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);
//创建一个定点坐标Buffer,一个float为4字节所以这里需要
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(pos.length*4);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer .put(pos);
vertexBuffer .position(0);
ByteBuffer cbyteBuffer = ByteBuffer.allocateDirect(colors.length*4);
cbyteBuffer.order(ByteOrder.nativeOrder());
coordBuffer = cbyteBuffer.asFloatBuffer();
coordBuffer .put(colors);
coordBuffer .position(0);
……………………
}
增加新的ByteBuffer储存颜色值信息
在
@Override
public void onDrawFrame(GL10 gl) {
//清空缓冲区,与 GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);对应
GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);
//使用OpenGL程序
GLES20.glUseProgram(program);
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
GLES20.glUniformMatrix4fv(vMatrix, 1 ,false, mMVPMatrix, 0);
//获取顶点着色器变量vPosition
int vPositionHandler = GLES20.glGetAttribLocation(program, "vPosition");
//允许使用顶点坐标数组
GLES20.glEnableVertexAttribArray(vPositionHandler);
//第一个参数顶点属性的索引值
// 第二个参数顶点属性的组件数量。必须为1、2、3或者4,如position是由3个(x,y,z)组成,而颜色是4个(r,g,b,a))
// 第三个参数数组中每个组件的数据类型
// 第四个参数指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)
// 第五个参数指定连续顶点属性之间的偏移量,这里由于是三个点 每个点4字节(float) 所以就是 3*4
// 第六个参数前面的顶点坐标数组
GLES20.glVertexAttribPointer(vPositionHandler, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);
int aColorHandler = GLES20.glGetAttribLocation(program, "aColor");
GLES20.glEnableVertexAttribArray(aColorHandler);
GLES20.glVertexAttribPointer(aColorHandler, 4, GLES20.GL_FLOAT, false, 0, coordBuffer);
//三角形绘制方式
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);
//禁止使用顶点坐标数组
GLES20.glDisableVertexAttribArray(vPositionHandler);
}
增加
int aColorHandler = GLES20.glGetAttribLocation(program, "aColor");
GLES20.glEnableVertexAttribArray(aColorHandler);
GLES20.glVertexAttribPointer(aColorHandler, 4, GLES20.GL_FLOAT, false, 0, coordBuffer);
下面看一下效果。
下面是完整代码
package pers.wtt.opengles10.render;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
/**
* Created by WT on 2018/4/8.
*/
public class DGLRender implements GLSurfaceView.Renderer {
//顶点着色器代码
private final String vertex = "" +
"attribute vec4 vPosition;" +
"uniform mat4 vMatrix;" +
"varying vec4 vColor;" +
"attribute vec4 aColor;" +
"void main(){" +
"gl_Position=vMatrix*vPosition;"+
"vColor=aColor;" +
"}";
//片元着色器代码
private final String fragment = "" +
"precision mediump float;" +
"varying vec4 vColor;" +
"void main(){" +
"gl_FragColor=vColor;"+
"}";
//顶点坐标
private final float[] pos = {
-0.5f,-0.5f,0.0f,
0.5f,0.5f,0.0f,
0.5f,-0.5f,0.0f,
};
//颜色值
private final float[] colors = {
1.0f,0.0f,0.0f,1.0f,
0.0f,1.0f,0.0f,1.0f,
0.0f,0.0f,1.0f,1.0f
};
//GL程序
int program;
//定点坐标Buffer
FloatBuffer vertexBuffer;
FloatBuffer coordBuffer;
float[] mMVPMatrix;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//将背景设置为灰色,这里只是设置,并没有立即生效
GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);
//创建一个定点坐标Buffer,一个float为4字节所以这里需要
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(pos.length*4);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer .put(pos);
vertexBuffer .position(0);
ByteBuffer cbyteBuffer = ByteBuffer.allocateDirect(colors.length*4);
cbyteBuffer.order(ByteOrder.nativeOrder());
coordBuffer = cbyteBuffer.asFloatBuffer();
coordBuffer .put(colors);
coordBuffer .position(0);
//装载顶点着色器和片元着色器,从source
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertex);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragment);
//创建Opengl程序,获取程序句柄,为了方便onDrawFrame方法使用所以声明为成员变量
program = GLES20.glCreateProgram();
//激活着色器
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
//链接程序
GLES20.glLinkProgram(program);
}
/**
* 装载着色器从资源代码,需要检测是否生成成功,暂时不检测
* @param type 着色器类型
* @param source 着色器代码源
* @return 返回着色器句柄
*/
private int loadShader(int type, String source) {
int shader = 0;
shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, source);
GLES20.glCompileShader(shader);
return shader;
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//计算屏幕宽高比
float ratio = (float)width/height;
//储存投影矩阵
float[] mPMatrix = new float[16];
//储存相机位置矩阵
float[] mVMatrix = new float[16];
//最终得到的矩阵
mMVPMatrix = new float[16];
//透视矩阵
//存储生成矩阵元素的float[]类型数组
//填充起始偏移量
//near面的left,right,bottom,top
//near面,far面与视点的距离
Matrix.frustumM(mPMatrix, 0, -ratio, ratio, -1,1,3, 6);
//存储生成矩阵元素的float[]类型数组
//填充起始偏移量
//摄像机位置X,Y,Z坐标
//观察目标X,Y,Z坐标
//up向量在X,Y,Z上的分量,也就是相机上方朝向,upY=1朝向手机上方,upX=1朝向手机右侧,upZ=1朝向与手机屏幕垂直
Matrix.setLookAtM(mVMatrix, 0, 0,0,6, 0,0,0,0,1,0);
//以上两个方法只能得到矩阵并不能使其生效
//下面通过矩阵计算得到最终想要的矩阵
//存放结果的总变换矩阵
//结果矩阵偏移量
//左矩阵
//左矩阵偏移量
//右矩阵
//右矩阵偏移量
Matrix.multiplyMM(mMVPMatrix, 0,mPMatrix, 0, mVMatrix, 0);
//当大小改变时重置视区大小
GLES20.glViewport(0,0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
//清空缓冲区,与 GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);对应
GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);
//使用OpenGL程序
GLES20.glUseProgram(program);
int vMatrix = GLES20.glGetUniformLocation(program, "vMatrix");
GLES20.glUniformMatrix4fv(vMatrix, 1 ,false, mMVPMatrix, 0);
//获取顶点着色器变量vPosition
int vPositionHandler = GLES20.glGetAttribLocation(program, "vPosition");
//允许使用顶点坐标数组
GLES20.glEnableVertexAttribArray(vPositionHandler);
//第一个参数顶点属性的索引值
// 第二个参数顶点属性的组件数量。必须为1、2、3或者4,如position是由3个(x,y,z)组成,而颜色是4个(r,g,b,a))
// 第三个参数数组中每个组件的数据类型
// 第四个参数指定当被访问时,固定点数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE)
// 第五个参数指定连续顶点属性之间的偏移量,这里由于是三个点 每个点4字节(float) 所以就是 3*4
// 第六个参数前面的顶点坐标数组
GLES20.glVertexAttribPointer(vPositionHandler, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);
int aColorHandler = GLES20.glGetAttribLocation(program, "aColor");
GLES20.glEnableVertexAttribArray(aColorHandler);
GLES20.glVertexAttribPointer(aColorHandler, 4, GLES20.GL_FLOAT, false, 0, coordBuffer);
//获取片元着色器变量vColor
//int vColor = GLES20.glGetUniformLocation(program, "vColor");
//GLES20.glUniform4fv(vColor, 1, colors, 0);
//三角形绘制方式
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3);
//禁止使用顶点坐标数组
GLES20.glDisableVertexAttribArray(vPositionHandler);
}
}