###
我们已经成功显示了橙色背景下绿色的三角形
最后得到的效果如图
可是如果你是自己设计的三角形,你应该会发现不太对劲,比如我这里设计的三角形顶点
static float triangleCoords[] = { // in counterclockwise order:
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
};
这怎么看都应该是接近正三角形啊,怎么显示之后窄了这么多?
###Opengl 坐标
如果你留意的话,应该记得当时设计三角形顶点时,有一条规则,数据都是归一化的。
所以我们设计的时候,想象中是左边的样子,但由于不是绝对坐标,而是归一化的相对坐标,在设备上显示时,图像按照屏幕比例拉伸了,也就显示了右边的图像。
所以我们要想自己展示的图片正常,就需要对图像进行缩放。
这个缩放过程可以用矩阵运算来实现,具体图形学原理可以去单独学习,OpenGL提供了接口可以直接修改,我们直接关注怎么使用就好了。
除了缩放问题影响显示效果,我们观察的位置也影响显示效果。比如我们现在看见的是一个三角形,但我们怎么知道这是一个金字塔还是只是一个平面三角形呢?
手机屏幕(几乎所有交互设备)是二维的,但我们存储的内容是三维的,我们将三维的内容投射到二维的窗口上,显然可以类比为一个摄像机身处三维空间中去观察物体,那么摄像机的位置,看的方向,和能看到的视场大小都会影响看见的内容。
opengl提供了两个接口来模拟摄像机(camera views)和进行伸缩变换(projection),Martix.setLookAtM和Martix.frustuM,他们本质上是根据输入参数产生两个矩阵,应用矩阵运算去模拟虚拟摄像机。
在开始学习这两个接口之前,我们先将之前已经完成的代码贴出来。
###已经完成的代码
Triangle类
import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
public class Triangle {
private FloatBuffer vertexBuffer;
public final int mProgram;
private int positionHandle;
private int colorHandle;
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX; //how many vertex
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
//shader代码
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;"+
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
// 数组中每个顶点的坐标数(即维数)
static final int COORDS_PER_VERTEX = 3;
static float triangleCoords[] = { // in counterclockwise order:
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
};
// 颜色信息,分别是RGB和alpha通道的归一化值
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
public Triangle() {
/**
* 将顶点数据传入Buffer
*/
// 初始化顶点数据Buffer
ByteBuffer bb = ByteBuffer.allocateDirect(
// (一个float型4字节)
triangleCoords.length * 4);
// 字节序使用native order
bb.order(ByteOrder.nativeOrder());
// 将ByteBuffer转换为浮点型FloatBuffer
vertexBuffer = bb.asFloatBuffer();
// 将顶点数据添加到Buffer
vertexBuffer.put(triangleCoords);
// Buffer位置调整到开头
vertexBuffer.position(0);
/**
* 创建shader并链接到程序
*/
int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// 创建空的Opengl ES program
mProgram = GLES20.glCreateProgram();
// 将顶点着色器加入program
GLES20.glAttachShader(mProgram, vertexShader);
// 将片元着色器加入program
GLES20.glAttachShader(mProgram, fragmentShader);
// 创造opengl ES可执行的文件
GLES20.glLinkProgram(mProgram);
}
public void draw() {
// 将program添加到Opengl ES环境
GLES20.glUseProgram(mProgram);
// 获取mProgram中vPosition的句柄(顶点数据)
positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 启用顶点属性
GLES20.glEnableVertexAttribArray(positionHandle);
// 顶点坐标的处理方式,参数依次为索引值(刚刚获取的句柄),数据维数(顶点即3维)
// 数据类型(float),当被访问时固定点数值是否需要归一化(false)
// 步长,即连续顶点偏移量(COORDS_PER_VERTEX * 4),
// 起始位置在缓冲区的偏移量(vertexBuffer)
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 获取mProgram中vColor的句柄(颜色数据)
colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 传入颜色矩阵
GLES20.glUniform4fv(colorHandle, 1, color, 0);
// 绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用顶点属性
GLES20.glDisableVertexAttribArray(positionHandle);
}
}
GLSurfaceView类
import android.content.Context;
import android.opengl.GLSurfaceView;
public class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRenderer renderer;
public MyGLSurfaceView(Context context) {
super(context);
//设置版本
setEGLContextClientVersion(2);
//创建Renderer实例
renderer = new MyGLRenderer();
//将GLRenderer和GLSurfaceView连接
setRenderer(renderer);
}
}
GLSurfaceView.Renderer类
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MyGLRenderer implements GLSurfaceView.Renderer {
private Triangle mTriangle;
//shader加载方法,返回编译好的shader
public static int loadShader(int type, String shaderCode){
// 创建顶点着色器的type是 GLES20.GL_VERTEX_SHADER
// 创建片元着色器的type是 GLES20.GL_FRAGMENT_SHADER
int shader = GLES20.glCreateShader(type);
// 传递着色器代码并编译着色器
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//设置背景颜色,参数是RGB和alpha通道值的归一值,即范围为0-1,我设置的是橙色
GLES20.glClearColor(1.0f, 0.6f, 0f, 1.0f);
mTriangle = new Triangle();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//调整窗口大小
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
//把窗口颜色用刚刚设定的值(glClearColor)刷洗
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//调用绘制接口画图
mTriangle.draw();
}
}
MainActivity类
import androidx.appcompat.app.AppCompatActivity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
private GLSurfaceView gLView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建GLSurfaceView实例
gLView = new MyGLSurfaceView(this);
//连接GLSurfaceView
setContentView(gLView);
}
}
###Go on
我们已经知道了图形的变换是矩阵运算,也就是我们需要求得与变换等价的矩阵,并作用在顶点数据上。那么我们假设这个矩阵名为uMVPMatrix,接下来它和原顶点矩阵作用就可以在opengl着色器代码中实现,在Triangle类中。
private final String vertexShaderCode =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// " gl_Position = vPosition;"+
" gl_Position = uMVPMatrix * vPosition; " +
"}";
我们将原来的位置信息乘上一个变换矩阵,注意第一行还添加了对uMVPMatrix的申明。
"uniform mat4 uMVPMatrix;" +
接下来我们只需要求出这个变换矩阵即可。
对于图像的变换(projection)和模拟摄像机(camera views),opengl ES给出了两个接口
#projection:Matrix.frustumM()
这个方法来自android.opengl.Matrix
frustumM
public static void frustumM (float[] m,
int offset,
float left,
float right,
float bottom,
float top,
float near,
float far)
Parameters | |
---|---|
m | float : the float array that holds the output perspective matrix
|
offset | int : the offset into float array m where the perspective matrix data is written |
它的作用是修改显示比例和修改前后视场距离,其中left、right、bottom和top参数和显示比例有关,near与far和视场截距有关。这几个参数信息将存储在第一个参数,即浮点数组中。第二个参数是记录于m的偏移量,一般一个数组只用于记录这些信息时填0即可。opengl中的Matrix是4x4的矩阵,所以数组大小要开到16。
m[offset + 0] m[offset + 4] m[offset + 8] m[offset + 12]
m[offset + 1] m[offset + 5] m[offset + 9] m[offset + 13]
m[offset + 2] m[offset + 6] m[offset + 10] m[offset + 14]
m[offset + 3] m[offset + 7] m[offset + 11] m[offset + 15]
修改显示比例:
left和right为左右显示比例,bottom和top为上下显示比例。
我们通常在Renderer类中onSurfaceChanged方法中设置这个参数,因为窗口大小改变时它能第一时间做出反应,并且这个方法提供了屏幕width和height的参数。
这四个数之间是按照比例来运作的,通常设置的方法如下所示,|left/bottom|=|ratio|=|width/height|,|bottom/height|=1,我们就将屏幕比例信息提取到矩阵中了。(注意值是有正负的)
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//调整窗口大小
GLES20.glViewport(0, 0, width, height);
/**
* projection matrix
*/
float ratio = (float) width / height;
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 2.5f, 30);
}
光是这个修改已经足够让之前的图像正常显示了(我们代码还没完成,只是说逻辑上比例的修改已经完成)。我提前放几张完成后不同参数的图像,来帮助我们理解这些参数。
#1:按照屏幕比例还原图像后
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 2.5f, 30);
可以看到,图像几乎是一个正三角形!我们正确的显示了。
#2:拉伸顶部
我们将top的值设置成了2,这时|top/bottom|=2
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 2, 2.5f, 30);
可以看到上面部分相对下面部分感觉被拉伸了。如果我们修改left和right等也能得到相同效果。
#3:符号取反
参数是有正负的,那么有什么意义呢?我们取反来试试。
原本需要为负的left,我们取了正值。
Matrix.frustumM(projectionMatrix, 0, ratio, ratio, -1, 2, 2.5f, 30);
这时报错 java.lang.IllegalArgumentException: left == right
看起来左边需要比右边小,那我们左边缩小一点,同样保证它是正值。
Matrix.frustumM(projectionMatrix, 0, ratio/2, ratio, -1, 2, 2.5f, 30);
发现只能看见三角形右边一点。如果想象一下应该是图形被拉伸了许多,并且图像被截取了。
这说明,四个参数有绝对的意义,并不是单纯的按照相互之间的比例来运行,这也就是建议左右或者上下参数设置为1的原因,如果我们将左右设置成width,上下设置成height也能达成正确的比例,但参数整体大下会影响到后面两个参数的值。就像地图的比例尺一样。
修改视场截距:
视场截距和最后两个参数near和far有关。
这两个距离确定了我们能看见的范围,如果物体在near距离内或在far距离外,我们就无法看见。只有在被截取的中间部分,我们才能看见。frustumM中frustum的意思就是截头锥体。
我没有查到资料(Opengl规范中应该有),但据我估计,需要显示的东西是以透视投影的方法投影在near这个面上的,所以即使都可以看见图像,但不同参数看见图像的大小不同。
还是用完成后的图像帮助我们理解这两个参数,这里我们摄像机的位置在(0,0,-3),等下就会用Matrix.setLookAtM设置摄像机。
#1:near十分接近图像
我们near设置在了2.9
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 2.9f, 30);
#2:near更靠近摄像机
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 1f, 30);
发现图像缩小了,符合透视投影的特点。
如果我们将near的距离设置在3之后,图像也就看不见了。
#camera views:Matrix.setLookAtM()
接下来看看如何去设置虚拟摄像机。
setLookAtM
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)
Parameters | |
---|---|
rm | float : returns the result
|
rmOffset | int : index into rm where the result matrix starts
|
eyeX | float : eye point X
|
eyeY | float : eye point Y
|
eyeZ | float : eye point Z
|
centerX | float : center of view X
|
centerY | float : center of view Y
|
centerZ | float : center of view Z
|
upX | float : up vector X
|
upY | float : up vector Y
|
upZ | float : up vector Z |
这个方法的参数看起来十分多,但其实也就需要三个坐标,前两个参数和Matrix.frustumM()一样,只是传入一个用来存储的数组,也需要开16的大小。后面九个参数分别为观察点(摄像机)位置,视场中心位置和摄像机顶部向量。
我们用人自己来理解这些参数即可。我们人的位置就是观察点位置,视场中心位置就是我们眼睛看向的方向,摄像机顶部向量也就是我们站立时从脚连接到头这个向量。
如果我们完全用摄像机来理解,可能会有点困难,摄像机只需要确定在空间中的位置和它对着的方向即可了,为什么还需要摄像机顶部的方向向量呢?或者摄像机顶部方向一旦确定,摄像机对着的方向不就确定了吗? 但我们把摄像机换成人的脑袋就好理解了,我们笔直站立的时候,头顶部向量垂直向上,但我们眼睛不一定看着正前方,我们可以到处瞟,即视场的中心可以不在正中心,但摄像机由于它的物理结构,对着什么方向就摄像什么方向。
我们依然举两个例子说明
Matrix.setLookAtM(viewMatrix, 0, 0f, 0f, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
在上面的情况下,摄像机在z轴-3的位置,图像在x-y平面上,摄像机顶部向量与y轴平行,所以摄像机镜头面对x-y平面,正面对三角形进行观察。
接下来我们稍微改变摄像机位置,以来理解参数的意义。(同样的,是提前放出完成后的图像)
#1:摄像机位置偏移
我们将摄像机的位置(eye point)移到(0.5,0.5,0),然后视场中心也设置到(0.5,0.5,0),那么我们应该看到的是向一个斜向方向平移了的三角形。
Matrix.setLookAtM(viewMatrix, 0, 0.5f, 0.5f, -3, 0.5f, 0.5f, 0f, 0f, 1.0f, 0.0f);
就不举过多的例子了,这个例子应该很说明问题了。
###实现剩下的代码
我们花了很长的篇幅去理解Matrix.setLookAtM()和Matrix.frustumM()接口。
这两个接口可以将我们需要的图像比例和摄像机位置信息存储在两个矩阵中(对我们而言是两个16大小的浮点数组)。
之后我们就已经设置好了一切和显示有关的信息,只需要将这两个矩阵信息传给opengl处理即可。
我们将两个矩阵相乘,合成一个矩阵,这时所有的变换由这一个矩阵实现。也就是将下列代码放在Renderer中的onDrawFrame下。当然vPMatrix这个量也是16大小的浮点数组,别忘了申明。
Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
之后将这个参数传给绘制接口draw()来绘制每一帧
mtriangle.draw(vPMatrix);
所以对应的draw也需要修改,首先接收我们的变换矩阵,再将变换矩阵传入到之前在着色器代码中定义的矩阵。
public void draw(float[] mvpMatrix) {
// 将program添加到Opengl ES环境
GLES20.glUseProgram(mProgram);
// 获取mProgram中vPosition的句柄(顶点数据)
positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 启用顶点属性
GLES20.glEnableVertexAttribArray(positionHandle);
// 顶点坐标的处理方式,参数依次为索引值(刚刚获取的句柄),数据维数(顶点即3维)
// 数据类型(float),当被访问时固定点数值是否需要归一化(false)
// 步长,即连续顶点偏移量(COORDS_PER_VERTEX * 4),
// 起始位置在缓冲区的偏移量(vertexBuffer)
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 获取mProgram中vColor的句柄(颜色数据)
colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 传入颜色矩阵
GLES20.glUniform4fv(colorHandle, 1, color, 0);
/**
* 添加projection和camera views作用
*/
// 获得uMVPMatrix矩阵(我们用来变换作用)的句柄
vPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// 将之前设置好的矩阵信息传到uMVPMatrix矩阵
GLES20.glUniformMatrix4fv(vPMatrixHandle, 1, false, mvpMatrix, 0);
// 绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用顶点属性
GLES20.glDisableVertexAttribArray(positionHandle);
}
这就大功告成了!
###
这篇文章篇幅很长,但绝大部分都只是在说明怎么使用Matrix.setLookAtM()和Matrix.frustumM()接口。
真正代码量其实不多,只需要根据我们的需求,得到两个变换矩阵,这两个变换矩阵通过相乘合成一个,再将这一个矩阵传到opengl的着色器代码中,交付给opengl处理即可。
###完整代码
Triangle类
import android.opengl.GLES20;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
public class Triangle {
private FloatBuffer vertexBuffer;
public final int mProgram;
private int positionHandle;
private int vPMatrixHandle;
private int colorHandle;
private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX; //how many vertex
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
//shader代码
private final String vertexShaderCode =
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// " gl_Position = vPosition;"+
" gl_Position = uMVPMatrix * vPosition; " +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
// 数组中每个顶点的坐标数(即维数)
static final int COORDS_PER_VERTEX = 3;
static float triangleCoords[] = { // in counterclockwise order:
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
};
// 颜色信息,分别是RGB和alpha通道的归一化值
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
public Triangle() {
/**
* 将顶点数据传入Buffer
*/
// 初始化顶点数据Buffer
ByteBuffer bb = ByteBuffer.allocateDirect(
// (一个float型4字节)
triangleCoords.length * 4);
// 字节序使用native order
bb.order(ByteOrder.nativeOrder());
// 将ByteBuffer转换为浮点型FloatBuffer
vertexBuffer = bb.asFloatBuffer();
// 将顶点数据添加到Buffer
vertexBuffer.put(triangleCoords);
// Buffer位置调整到开头
vertexBuffer.position(0);
/**
* 创建shader并链接到程序
*/
int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
vertexShaderCode);
int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
fragmentShaderCode);
// 创建空的Opengl ES program
mProgram = GLES20.glCreateProgram();
// 将顶点着色器加入program
GLES20.glAttachShader(mProgram, vertexShader);
// 将片元着色器加入program
GLES20.glAttachShader(mProgram, fragmentShader);
// 创造opengl ES可执行的文件
GLES20.glLinkProgram(mProgram);
}
public void draw(float[] mvpMatrix) {
// 将program添加到Opengl ES环境
GLES20.glUseProgram(mProgram);
// 获取mProgram中vPosition的句柄(顶点数据)
positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// 启用顶点属性
GLES20.glEnableVertexAttribArray(positionHandle);
// 顶点坐标的处理方式,参数依次为索引值(刚刚获取的句柄),数据维数(顶点即3维)
// 数据类型(float),当被访问时固定点数值是否需要归一化(false)
// 步长,即连续顶点偏移量(COORDS_PER_VERTEX * 4),
// 起始位置在缓冲区的偏移量(vertexBuffer)
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
vertexStride, vertexBuffer);
// 获取mProgram中vColor的句柄(颜色数据)
colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
// 传入颜色矩阵
GLES20.glUniform4fv(colorHandle, 1, color, 0);
/**
* 添加projection和camera views作用
*/
// 获得uMVPMatrix矩阵(我们用来变换作用)的句柄
vPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
// 将之前设置好的矩阵信息传到uMVPMatrix矩阵
GLES20.glUniformMatrix4fv(vPMatrixHandle, 1, false, mvpMatrix, 0);
// 绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
// 禁用顶点属性
GLES20.glDisableVertexAttribArray(positionHandle);
}
}
Renderer类
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class MyGLRenderer implements GLSurfaceView.Renderer {
private Triangle mTriangle;
private final float[] vPMatrix = new float[16];
private final float[] projectionMatrix = new float[16];
private final float[] viewMatrix = new float[16];
//shader加载方法,返回编译好的shader
public static int loadShader(int type, String shaderCode){
// 创建顶点着色器的type是 GLES20.GL_VERTEX_SHADER
// 创建片元着色器的type是 GLES20.GL_FRAGMENT_SHADER
int shader = GLES20.glCreateShader(type);
// 传递着色器代码并编译着色器
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//设置背景颜色,参数是RGB和alpha通道值的归一值,即范围为0-1,我设置的是橙色
GLES20.glClearColor(1.0f, 0.6f, 0f, 1.0f);
mTriangle = new Triangle();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//调整窗口大小
GLES20.glViewport(0, 0, width, height);
/**
* projection matrix
*/
float ratio = (float) width / height;
Matrix.frustumM(projectionMatrix, 0, -ratio, ratio, -1, 1, 2.5f, 30);
}
@Override
public void onDrawFrame(GL10 gl) {
//把窗口颜色用刚刚设定的值(glClearColor)刷洗
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//设置camera views
Matrix.setLookAtM(viewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
//将两个矩阵合成一个
Matrix.multiplyMM(vPMatrix, 0, projectionMatrix, 0, viewMatrix, 0);
//调用绘制接口画图
// mTriangle.draw();
mTriangle.draw(vPMatrix);
}
}
GLSurfaceView类
import android.content.Context;
import android.opengl.GLSurfaceView;
public class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRenderer renderer;
public MyGLSurfaceView(Context context) {
super(context);
//设置版本
setEGLContextClientVersion(2);
//创建Renderer实例
renderer = new MyGLRenderer();
//将GLRenderer和GLSurfaceView连接
setRenderer(renderer);
}
}
MainActivity类
import androidx.appcompat.app.AppCompatActivity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
private GLSurfaceView gLView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建GLSurfaceView实例
gLView = new MyGLSurfaceView(this);
//连接GLSurfaceView
setContentView(gLView);
}
}