上篇文章,我们已经绘制出了一个直角三角形,但是我们的坐标明明设置是等腰直角的三角形,但实际上不是,这是为什么呢?原因是,手机屏幕宽高不等,没有做坐标变换,所以导致不是等腰直角三角形。那要如何坐标变换呢?那要涉及到变换矩阵、相机和投影了。
矩阵
在数学中,矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合 ,最早来自于方程组的系数及常数所构成的方阵。这一概念由19世纪英国数学家凯利首先提出。
相机
根据现实生活中的经历,对一个场景,随着相机的位置、方向的不同,拍摄出来的画面也是不相同。将相机对应于OpenGL世界,决定相机拍摄的结果(也就是最后屏幕上展示的结果),包括相机位置、相机观察方向以及相机的UP方向、
相机位置:可以理解为相机在3D空间里面的坐标点
相机观察方向:表示镜头的朝向
相机UP方向:可以理解为相机顶端指向的方向
在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世界,最后还需要呈现到一个2D平面上,这就是投影。投影有两种:正交投影、透视投影
正交投影
使用正交投影,物体呈现出来的大小不会随着居里点的远近而发生变化,在Android OpenGLES 程序中,我们可以通过以下方法来进行设置正交投影:
Matrix.orthoM ( float[] m, //接收正交投影的变换矩阵
int mOffset, //变换矩阵的起始位置(偏移量)
float left, //相对观察点近面的左边距
float right, //相对观察点近面的右边距
float bottom, //相对观察点近面的下边距
float top, //相对观察点近面的上边距
float near, //相对观察点近面距离
float far) //相对观察点远面距离
透视投影
使用透视投影,物体离视点越远,呈现出来的越小。离视点越近,呈现出来的越大。在Android OpenGLES 程序中,我们可以通过以下方法来进行设置透视投影:
Matrix.frustumM ( float[] m, //接收透视投影的变换矩阵
int mOffset, //变换矩阵的起始位置(偏移量)
float left, //相对观察点近面的左边距
float right, //相对观察点近面的右边距
float bottom, //相对观察点近面的下边距
float top, //相对观察点近面的上边距
float near, //相对观察点近面距离
float far) //相对观察点远面距离
使用变换矩阵
实际上相机设置和投影设置并不是真正的设置,而是通过设置参数来得到一个使用相机后顶点坐标的变换矩阵,和投影下的顶点坐标变换矩阵,我们还需把矩阵传入给顶点着色器,在顶点着色器中传入矩阵乘以坐标的向量,得到实际展示的坐标向量。注意,是矩阵乘以向量,不是坐标向量乘以矩阵,矩阵乘法是不满足交换律的。
而通过上面的相机设置和投影设置,我们得到的是两个矩阵,为了方便,我们还需要将相机矩阵和投影矩阵相乘,得到一个实际的变换矩阵,再传给顶点着色器。矩阵相乘:
Matrix.multiplyMM ( float[] result, //接收相乘结果
int resultOffset, //接收矩阵的起始位置(偏移量)
float[] lhs, //左矩阵
int lhsOffset, //左矩阵的起始位置(偏移量)
float[] rhs, //右矩阵
int rhsOffset) //右矩阵的起始位置(偏移量)
等腰直角三角形的实现
在上篇文章基础上,我们需要做以下步骤即可实现绘制一个等腰直角三角形:
1、修改顶点着色器,增加矩阵变换:
private final String vertextShaderCode =
"attribute vec4 vPosition;"+
"uniform mat4 vMatrix;"+
"void main() {"+
" gl_Position = vMatrix * vPosition;"+
"}";
2、设置相机和投影,获取相机矩阵和投影矩阵,然后通过相机矩阵与投影矩阵相乘,得到实际变换矩阵:
@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
{
GLES20.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,0,0,0,0,1.0f,0);
//计算变换矩阵
Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
}
3、将变换矩阵传入顶点着色器:
@Override
public void onDrawFrame(GL10 gl)
{
if(program == 0)
return;
//获取变换矩阵vMatrix成员句柄
int vMatrix = GLES20.glGetUniformLocation(program,"vMatrix");
//设置vMatrix的值
GLES20.glUniformMatrix4fv(vMatrix,1,false,mMVPMatrix,0);
//获取顶点着色器的vPosition成员句柄
int vPosition = GLES20.glGetAttribLocation(program, "vPosition");
//启用vPosition句柄
GLES20.glEnableVertexAttribArray(vPosition);
//传入但侥幸的坐标数据
GLES20.glVertexAttribPointer(vPosition,3,GLES20.GL_FLOAT,false,3*4, vertexBuffer);
//获取片元着色器的vColor成员句柄
int vColor = GLES20.glGetUniformLocation(program, "vColor");
//设置绘制三角形的颜色
GLES20.glUniform4fv(vColor,1,color,0);
//绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,3);
//禁止顶点数组的句柄
GLES20.glDisableVertexAttribArray(vPosition);
}
运行即可得到一个等腰直角三角形:
彩色三角形
老是显示一个白色的三角形实在太单调了,我们需要让这个三角形变成彩色,该怎么做?
片元着色是针对片元颜色的,针对片元执行一次。而在我们的片元着色器中,我们是直接给片元着色器赋值的,外部只传入一个颜色值,要使三角形呈现出彩色,我们需要在不同的片元赋值不同的颜色。为了简单处理,我们在上个等腰直角三角形的实例中修改顶点着色器跟片元着色器,达到三角形呈现为彩色:
顶点着色器
private final String vertextShaderCode =
"attribute vec4 vPosition;"+
"uniform mat4 vMatrix;"+
"attribute vec4 aColor;"+
"varying vec4 vColor;"+
"void main() {"+
" gl_Position = vMatrix * vPosition;"+
" vColor = aColor;"+
"}";
可以看到增加已一个aColor(顶点颜色)作为输入量,传递给vColor。vColor的前面有个varying 。像attribute、uniform、varying都是在OpenGL的着色器中表示限定符,
attribute一般用于每个顶点都各不相同的量。
只能在顶点着色器中使用
应用场景:传递顶点的位置、法线、颜色、纹理坐标。
uniform一般用于对同一组顶点组成的3D物理中的各个顶点都相同的量。
在 顶点着色器 和 片元着色器中是共享的,相当于一个全局的变量。
应用场景:传递 MVP 矩阵、光照位置、颜色等全局参数。
varying一般用于从顶点着色器传入片元着色器的量。
应用场景:纹理坐标、光照计算信息、法线等。
还有个const表示常量。
片元着色器
private final String fragmentShaderCode =
"precision mediump float;"+
"varying vec4 vColor;"+
"void main() {"+
" gl_FragColor = vColor;"+
"}";
vColor前面的限定符改为varying,接收顶点着色器传过来的量
然后,我们需要传入三个不同的顶点颜色到顶点着色器中:
//颜色
private float color[] = {
0.0f, 1.0f, 0.0f, 1.0f ,
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f
};
//申请底层空间
ByteBuffer colorBuffer = ByteBuffer.allocateDirect(color.length * 4);
colorBuffer.order(ByteOrder.nativeOrder());
colorFloatBuffer = colorBuffer.asFloatBuffer();
colorFloatBuffer.put(color);
colorFloatBuffer.position(0);
//获取顶点着色器的vColor成员句柄
int aColor = GLES20.glGetAttribLocation(program, "aColor");
//启用aColor句柄
GLES20.glEnableVertexAttribArray(aColor);
//设置绘制三角形的颜色
GLES20.glVertexAttribPointer(aColor,4,GLES20.GL_FLOAT,false,4*4,colorFloatBuffer);
运行得到一个彩色的等腰直角三角形: