Android OpenGl学习(一)

it2022-05-06  7

最近由于项目需要,所以开始学习OpenGL,网络上的东西零零散散,所以就想写一系列博客来记录学习OpenGL。

一、简介

首先我们要对其有一个简单的认识

什么是OpenGL?

官方描述:OpenGL是一个跨平台的图形API,用于指定3D图形处理硬件中的标准软件接口。

OpenGL的优势?

OpenGl是用来做图像处理的,那我们为什么不使用Canvas呢?费劲学这个干嘛?

答案很简单,为了效率,使用Canvas画图使用的是CPU,那我们都知道手机上还有一个专门的图形处理单元叫GPU,它可以并行的做浮点运算,OpenGL使用的就是GPU,这样的话可以用GPU分担CPU的工作量,提高图形渲染效率。

概念

顶点着色器:它的作用就是为每一个顶点生成坐标,因此每个顶点都要运行一遍顶点着色器程序,一旦顶点坐标计算出来之后,OpenGL就能够使用这些顶点来组成点,线,面。

片段着色器:它的作用就是为每一个顶点的片段渲染颜色。

坐标系:原点在中心,范围为[-1,1],2d环境只有(x,y),3d环境有(x,y,z)

二、画个三角形

1.创建GLSurfaceView

了解了基础概念之后我们就开始上代码,要使用OpenGL进行绘制就必须要有一个地方来供它绘制,它既然是使用GPU进行计算的,那这块地方就肯定与Canvas有所不同,这个地方就是GLSurfaceView,它是android为我们提供的一个类,继承于SurfaceView,也就是说它管理着一块Surface。

要创建SurfaceView可以使用new来创建,也可以在xml中书写

GLSurfaceView glSurfaceView=new GLSurfaceView(context); setContentView(glSurfaceView);

或者

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.opengl.GLSurfaceView android:id="@+id/glSurfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> GLSurfaceView glSurfaceView = (GLSurfaceView) findViewById(R.id.glSurfaceView);

创建完成后还要为其设定要使用的OpenGL版本,一般来说我们为其设置版本好为2

glSurfaceView.setEGLContextClientVersion(2);

为其设置Render,这个才是控制渲染的地方

glSurfaceView.setRenderer(renderer);

2.书写Render

Render是一个接口,存在于GLSurfaceView类中,你需要将它里面的三个重要的方法实现

public interface Renderer { //当GlSurfaceView创建时被调用 void onSurfaceCreated(GL10 var1, EGLConfig var2); //当GlSurfaceView尺寸发生变化时被调用 void onSurfaceChanged(GL10 var1, int var2, int var3); //当画每一帧的时候都会被调用 void onDrawFrame(GL10 var1); }

一般来说我们在onSurfaceCreated中绘制背景,在onSurfaceChanged中设置视图大小,在onDrawFrame中进行真正的绘画,不过要在绘画前清空视图,下面给一个简单的示例:

public class MyRender implements GLSurfaceView.Renderer { @Override public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f); } @Override public void onSurfaceChanged(GL10 gl10, int width, int height) { GLES20.glViewport(0,0,width,height); } @Override public void onDrawFrame(GL10 gl10) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT); } }

该Render绘制出来就是一个灰色的背景,上面没有任何图形,要绘制图形的话还要使用Shader,比如说我们要绘制三角形,那么它的ShaderCode就应该这么写

private final String vertexShaderCode= "attribute vec4 vPosition;"+ "void main(){"+ " gl_Position=vPosition;"+ "}";

当然还要给图形上色了,涂颜色也需要使用ShaderCode

private final String fragmentShaderCode= "precision mediump float;"+ "uniform vec4 vColor;"+ "void main(){"+ " gl_FragColor=vColor;"+ "}";

现在你先不用着急去研究Shader该怎么写,先熟悉整个流程

现在有了画图的程序和上色的程序还缺什么呢,缺少坐标,你不给坐标程序怎么知道你要画在哪,怎么画

//用于装载坐标点 private FloatBuffer vertexBuffer; //编译ShaderCode后程序的指针 private int mProgram; //一个坐标要用三个数表示,x,y,z private final int COORDS_PER_VERTEX=3; //坐标数组 private float triangleCoords[]={ 0.5f,0.5f,0.0f, -0.5f,-0.5f,0.0f, 0.5f,-0.5f,0.0f }; //顶点句柄 private int mPositionHandle; //颜色句柄 private int mColorHandle; //坐标点数量 private final int vertexCount=triangleCoords.length/COORDS_PER_VERTEX; //一个float占四位,一共占坐标点数量*4位的空间 private final int vertexStride=COORDS_PER_VERTEX*4; private float[] color={1.0f,1.0f,1.0f,1.0f};

接下来就是真正的编译程序和绘画了

@Override public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) { //设置背景 GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f); //初始化ByteBuffer,为其分配空间 ByteBuffer byteBuffer=ByteBuffer.allocateDirect(triangleCoords.length*4); //设置排列顺序为nativeOrder byteBuffer.order(ByteOrder.nativeOrder()); //将ByteBuffer转换为FloatBuffer vertexBuffer=byteBuffer.asFloatBuffer(); //将坐标点放入FloatBuffer vertexBuffer.put(triangleCoords); //设置起点 vertexBuffer.position(0); //获得编译后的顶点程序句柄 int vertexShader=loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode); //获得编译后的颜色程序句柄 int fragmentShader=loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode); //创建一个OpenGLES程序 mProgram=GLES20.glCreateProgram(); //将编译后的顶点程序加入其中 GLES20.glAttachShader(mProgram,vertexShader); //将编译后的颜色程序加入其中 GLES20.glAttachShader(mProgram,fragmentShader); //连接程序 GLES20.glLinkProgram(mProgram); //声明一个数组,用于存放连接程序结果 int[] linkStatus = new int[1]; //获取连接程序结果,将其存入linkStatus的第0个位置 GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0); //对连接程序结果的处理 if (linkStatus[0] != GLES20.GL_TRUE) { Log.e("ES20_ERROR", "Could not link program: "); Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(mProgram)); GLES20.glDeleteProgram(mProgram); mProgram = 0; } } @Override public void onSurfaceChanged(GL10 gl10, int width, int height) { //设置视图大小 GLES20.glViewport(0,0,width,height); } @Override public void onDrawFrame(GL10 gl10) { //清空视图 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT); //将程序加入到OpenGLES环境 GLES20.glUseProgram(mProgram); //获取顶点着色器的句柄 mPositionHandle=GLES20.glGetAttribLocation(mProgram,"vPosition"); //启用句柄 GLES20.glEnableVertexAttribArray(mPositionHandle); //填入数据 GLES20.glVertexAttribPointer(mPositionHandle,COORDS_PER_VERTEX, GLES20.GL_FLOAT,false,vertexStride,vertexBuffer); //获取片元着色器的句柄 mColorHandle=GLES20.glGetUniformLocation(mProgram,"vColor"); //填入数据 GLES20.glUniform4fv(mColorHandle,1,color,0); //开始绘制 GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,vertexCount); //禁用顶点着色器的句柄 GLES20.glDisableVertexAttribArray(mPositionHandle); } //根据类型加载ShaderCode程序 protected int loadShader(int type, String shaderCode){ //根据类型创建着色器 int shader= GLES20.glCreateShader(type); //加入代码 GLES20.glShaderSource(shader,shaderCode); //开始编译 GLES20.glCompileShader(shader); //声明一个数组,用于存放编译结果 int[] compiled = new int[1]; //获取编译结果,将其存入到compiled的第0个位置中 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); //根据编译结果做处理 if (compiled[0] == 0) { Log.e("ES20_ERROR", "Could not compile shader " + type + " : "+shaderCode); Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); shader = 0; } return shader; }

这样一个三角就画好了,但是你会发现画出来的三角形和填入的坐标并不相符,明明应该是一个等腰直角三角形,为什么被拉长了呢?因为你的屏幕不是方的,你的屏幕要是方的它就是一个等腰直角三角形了,所以怎么才能把它转换一下呢?这就涉及到了投影和相机视图了,下篇我会讲一下。

项目地址:https://github.com/s15603333319/AndroidOpenGL


最新回复(0)