本文正在参与「金石计划」

大家好,我是小余,本篇文章是OpenGL ES系列的榜首篇文章。首要通过一个根底的三角形制作来带大家了解OpenGL ES的根底知识,作为一个入门文章。

OpenGLES是什么?

OpenGLES是OpenGL的一个子集,也便是OpenGL的一个精简指令集,首要用于嵌入式设备,如手机,平板等,本质上是一个跨编程言语,跨渠道的编程接口标准。注意是标准,或许你简略能够理解为便是一个接口API。

【安卓音视频开发升级打怪之路】 开发入门(一):初识OpenGLES

OpenGLES如何作业的?

讲到如何作业,一定要知道OpenGl的图形烘托管线,指将一些根底数据,如极点数据,色彩,纹路等数据作为输入,经过多个改变处理,终究输出到屏幕上一个进程。这个进程大致包括:极点着色器,图元装备,几许着色器,光栅化,片段着色器,测试与混合这六个阶段

【安卓音视频开发升级打怪之路】 开发入门(一):初识OpenGLES

关于咱们GLES来说,大部分状况咱们只需求和极点着色器以及片段着色器打交道,其他的咱们暂时不需求去了解。

极点着色器

极点着色器的首要作用是将3D坐标转换为别的一个坐标,同时极点着色器答应咱们对极点特点进行一些根本处理。极点着色器是几个可编程着色器之一。

输入:3D坐标数据

输出:改变后的坐标数据

编程言语:GLSL

片段着色器

片段着色器是一个给你的片段进行上色的一个步骤。假设需求对不同片段进行不同的处理,那么就需求获取当时片段的方位坐标,如何获取呢?还记得刚才说的极点着色器么,他能够输出每个片段的方位特点信息,然后在片段着色器中作为输入特点就能够了。 输入:各种特点数据,不只限于方位特点,关于一些需求光照场景,还会添加一个物体原料,法向量,光照特点等

输出:当时片段的色彩值

编程言语:GLSL

GLSL是什么? GLSL(OpenGL Shading Language),着色器编程言语,也便是说用来编写咱们着色器的。他的写法和C言语的写法很相似。

下面是一个最简略的极点着色器的编写:

#version 300 es
layout (location = 0) in vec3 position;
void main()
{
  gl_Position = vec4(position, 1.0f);
}
  • 第1行:用来指定当时运用的GLES的版别

  • 第2行:in代表这个是一个输入特点,position是当时特点的name,这个自己随意指定,vec3表明当时的position特点是一个三位坐标系,layout (location = 0):表明当时特点的location值为0,在外部指定特点编号的时分会用到,后边讲demo的时分会提到。

  • 第5行:main里边的gl_Position代表当时极点方位特点的输出,这个称号不能更改,后边处理会默许运用这个称号。此处的gl_Position代表一个4重量的向量,注意最终一个值并非方位重量,而是用来处理透视划分运用到的,暂时不用去深入,默许为1.0.

既然是编程言语那就需求去编译。

编译着色器

首先编译着色器之前咱们需求去创立一个着色器目标,gl api中现已给咱们供给了:

GLuint vertexShader; 
vertexShader = glCreateShader(GL_VERTEX_SHADER);

这儿的vertexShader便是一个着色器目标的索引ID。GL_VERTEX_SHADER:表明当时创立的是一个极点着色器。

其次咱们需求将着色器源码附加到这个着色器目标中:

glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);

第三个参数vertexShaderSource表明GLSL源码地址,最终编译:

glCompileShader(vertexShader);

关于片段着色器也是同样的编译方法。

那编译完结之后就能够运用了么?

着色器程序

着色器程序目标是多个着色器合并之后,链接的版别,前面创立的着色器目标需求运用,一定需求将他们链接到一个着色器目标上之后才能运用。如何链接?

首先咱们需求创立一个着色器程序:

GLuint shaderProgram;
shaderProgram = glCreateProgram();

然后用glLinkProgram链接着色器目标:

glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

最终在制作之前调用:

glUseProgram(shaderProgram);

之后所有的操作都是针对前面编写的着色器目标了。

链接极点特点

极点着色器其实不止会传入极点数据作为输入,比如对色彩以及法向量等也能够运用极点数组的方法传入。

所以就需求区别当时输入的数据对应的到底是哪一个特点。

一般咱们的极点数据会被解析成下面这种方法:

【安卓音视频开发升级打怪之路】 开发入门(一):初识OpenGLES

  • 方位数据被储存为32-bit(4字节)浮点值
  • 每个方位包含3个这样的值。每个方位代表当时x,y,z重量
  • 这几个值在数组中严密摆放,没有缝隙。
  • 数据中的榜首个值在缓冲开端的方位。

有了上面的极点数组信息今后,咱们就能够运用OpenGl给咱们供给的特点api来指定特点了。

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer参数界说:

  • ** 榜首个参数0**:你或许还记得咱们前面分析极点着色器的时分说的那个location值,那个location值便是在这儿运用的,当指定为0,着色器目标将会将当时特点分配给着色器中对应location为0的变量。
  • 第二个参数3:指定了当时特点占用的长度,关于方位特点,咱们一般运用vec3类型,也便是3重量x,y,z来表明,所以这儿咱们指定长度为3,一些其他状况,如纹路坐标,这儿就或许为2了,由于只需求用到纹路坐标只需求运用到x和y重量。
  • 第3个参数GL_FLOAT:指定了数据的类型,GLSL中vec*都是由浮点数值组成的。
  • 第4个参数GL_FALSE:是否期望数据被标准化,标准化后的数据都会在0和1之间或许-1和1之间。
  • 第5个参数3 * sizeof(GLfloat):表明到下一个同类型的特点的内存间隙,这儿咱们只要一种特点,且特点的长度为3,所以间隔为3,这儿说的是内存之间的间隔,所以运用了3 * sizeof(GLfloat)。
  • ** 第6个参数(GLvoid*)0**:表明离起始方位的偏移量,这儿只要一种特点,所以偏移量为0,如果有多种特点,那么就不会只为0了。

glEnableVertexAttribArray(0):表明启用当时location为0的极点特点,默许关闭。

VAO与VBO

VBO:极点缓冲目标,在极点着色器处理阶段,咱们运用VBO,在GPU上提早创立一块内存,用于缓存极点数据。这样能够避免在每次制作的时分都需求从头发送数据给GPU,究竟这是一个比较耗时的操作。

VAO:极点数组目标,VAO的首要作用是用来管理VBO或许EBO, ,削减 glBindBuffer 、glEnableVertexAttribArray、 glVertexAttribPointer 这些调用操作,提高极点数组切换步骤。

VAO 与 VBO 之间的联系

【安卓音视频开发升级打怪之路】 开发入门(一):初识OpenGLES

有了以下根底之后,咱们再来制作一个三角形

制作一个三角形

应用层咱们运用GLSurfaceView作为前言,由于其内部封装了EGL环境的建立,所以咱们只需求把它引入到咱们的项目中即可。

public class MyGLSurfaceView extends GLSurfaceView {
​
  Renderer mRenderer;
  public MyGLSurfaceView(Context context) {
    this(context,null);
   }
​
  public MyGLSurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setEGLContextClientVersion(2);
    setEGLConfigChooser(8,8,8,8,16,8);
   }
​
  public void setmRenderer(MyGLSurfaceRenderer mRenderer) {
    this.mRenderer = mRenderer;
    setRenderer(mRenderer);
    setRenderMode(mRenderer.rendererMode);
   }
​
}

咱们还自界说了个Renderer

public class MyGLSurfaceRenderer implements GLSurfaceView.Renderer {
  int rendererMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY;
  MyNativeRenderer nativeRenderer;
  MyGLSurfaceRenderer(){
    nativeRenderer = new MyNativeRenderer();
   }
  @Override
  public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
    nativeRenderer.native_onSurfaceCreated();
   }
​
  @Override
  public void onSurfaceChanged(GL10 gl10, int width, int height) {
    nativeRenderer.native_onSurfaceChanged(width,height);
   }
​
  @Override
  public void onDrawFrame(GL10 gl10) {
    nativeRenderer.native_onDrawFrame();
   }
​
  public void destroy(){
    nativeRenderer.native_destroy();
   }
}

并建立一个MyNativeRenderer用来和native层进行操作,直接运用的是native层的opengles接口进行开发。

其实也能够运用java层的GLES接口进行处理,可是这种方法一方面移植性不好,opengl仍是有许多优异的开源c/c++库,运用java来开发就无法移植了,所以小余选择运用了native层进行开发,别的一方面也是为了巩固自己对jni层的理解。

public class MyNativeRenderer {
  static {
    System.loadLibrary("opengl-es-lib");
   }
  public MyNativeRenderer(){
​
   }
  public native void native_onSurfaceCreated();
  public native void native_onSurfaceChanged(int width,int height);
  public native void native_onDrawFrame();
  public native void native_destroy();
​
}

在jni层,咱们引入GLES类库,代码如下:

MyGLRenderContext* MyGLRenderContext::context = nullptr;
//单例类
MyGLRenderContext * MyGLRenderContext::getInstance() {
  if(context == nullptr){
    context = new MyGLRenderContext();
   }
  return context;
}
void MyGLRenderContext::OnSurfaceCreated() {
  glClearColor(0.1f,0.2f,0.3f,1.0f);
​
}
​
void MyGLRenderContext::OnSurfaceChanged(int width, int height) {
  glViewport(0,0,width,height);
}
/**
 * 制作前操作
 * 0.初始化极点数据
 * 1.创立着色器程序目标
 * 2.生成VAO,VBO目标
 * */
void MyGLRenderContext::beforeDraw() {
  if(programObj!= 0){
    return;
   }
  //0.初始化极点数据
  GLfloat vertices[] = {
      0.0f, 0.5f, 0.0f,1.0f,0.0f,0.0f,
      -0.5f,-0.5f, 0.0f,0.0f,1.0f,0.0f,
      0.5f, -0.5f, 0.0f,0.0f,0.0f,1.0f
   };
  //1.创立着色器程序,此处将着色器程序创立封装到一个工具类中
  char vShaderStr[] =
      "#version 300 es              \n"
      "layout(location = 0) in vec4 vPosition;  \n"
      "layout(location = 1) in vec3 vColor;  \n"
      "out vec3 color;  \n"
      "void main()                \n"
      "{                     \n"
      "  gl_Position = vPosition;        \n"
      "  color = vColor;        \n"
      "}                     \n";
​
  char fShaderStr[] =
      "#version 300 es                \n"
      "precision mediump float;           \n"
      "in vec3 color;              \n"
      "out vec4 fragColor;              \n"
      "void main()                  \n"
      "{                       \n"
      "  fragColor = vec4 (color, 1.0 );  \n"
      "}                       \n";
​
  programObj = GLUtils::CreateProgram(vShaderStr,fShaderStr);
​
  //2.生成VAO,VBO目标,并绑定极点特点
  GLuint VBO;
  glGenVertexArrays(1,&VAO);
  glGenBuffers(1,&VBO);
​
  glBindVertexArray(VAO);
  glBindBuffer(GL_ARRAY_BUFFER,VBO);
  glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);
​
  //极点坐标特点
  glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(GLfloat),(GLvoid*)0);
  glEnableVertexAttribArray(0);
  //极点色彩特点
  glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(GLfloat),(GLvoid*)(3*sizeof(GLfloat)));
  glEnableVertexAttribArray(1);
​
  glBindVertexArray(GL_NONE);
}
/**
 * 1.铲除buffer
 * 2.运用程序着色器目标
 * 3.开端制作
 * 4.解绑
 * */
void MyGLRenderContext::OnDrawFrame() {
  beforeDraw();
  if(programObj == 0){
    return;
   }
  //铲除buffer
  glClear(GL_COLOR_BUFFER_BIT);
  glClearColor(0.3f,0.5f,0.4f,1.0f);
  //运用程序着色器目标
  glUseProgram(programObj);
  //绑定VAO
  glBindVertexArray(VAO);
  //开端制作
  glDrawArrays(GL_TRIANGLES,0,3);
  //解绑VAO
  glBindVertexArray(GL_NONE);
  //解绑程序着色器目标
  glUseProgram(GL_NONE);
}
void MyGLRenderContext::destroy() {
  if(programObj){
    programObj = GL_NONE;
   }
  glDeleteVertexArrays(1,&VAO);
}

咱们运用一个单列类来处理三角形的初始化以及制作操作,由于着色器的创立以及以及链接操作都是共用的,咱们将其封装在一个GLUtils的类中:

GLuint GLUtils::CreateProgram(const char *pVertexShaderSource, const char *pFragShaderSource, GLuint &vertexShaderHandle, GLuint &fragShaderHandle)
{
    GLuint program = 0;
    FUN_BEGIN_TIME("GLUtils::CreateProgram")
        vertexShaderHandle = LoadShader(GL_VERTEX_SHADER, pVertexShaderSource);
        if (!vertexShaderHandle) return program;
        fragShaderHandle = LoadShader(GL_FRAGMENT_SHADER, pFragShaderSource);
        if (!fragShaderHandle) return program;
        program = glCreateProgram();
        if (program)
        {
            glAttachShader(program, vertexShaderHandle);
            CheckGLError("glAttachShader");
            glAttachShader(program, fragShaderHandle);
            CheckGLError("glAttachShader");
            glLinkProgram(program);
            GLint linkStatus = GL_FALSE;
            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
            glDetachShader(program, vertexShaderHandle);
            glDeleteShader(vertexShaderHandle);
            vertexShaderHandle = 0;
            glDetachShader(program, fragShaderHandle);
            glDeleteShader(fragShaderHandle);
            fragShaderHandle = 0;
            if (linkStatus != GL_TRUE)
            {
                GLint bufLength = 0;
                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
                if (bufLength)
                {
                    char* buf = (char*) malloc((size_t)bufLength);
                    if (buf)
                    {
                        glGetProgramInfoLog(program, bufLength, NULL, buf);
                        LOGCATE("GLUtils::CreateProgram Could not link program:\n%s\n", buf);
                        free(buf);
                    }
                }
                glDeleteProgram(program);
                program = 0;
            }
        }
    FUN_END_TIME("GLUtils::CreateProgram")
    LOGCATE("GLUtils::CreateProgram program = %d", program);
   return program;
}

结合前面给的一些根底理论知识,相信你是能看懂的。 作用

【安卓音视频开发升级打怪之路】 开发入门(一):初识OpenGLES

代码现已上传到github:需求的自行下载。

好了,本篇文章仅仅OpenGLES的入门文章,后续仍是推出其他相关文章。

别的自己整理了一些关于Android开发进阶以及面试的一些指导: 重视小余,回复“材料”免费获取。