本文正在参与「金石计划」
大家好,我是小余,本篇文章是OpenGL ES系列的榜首篇文章。首要通过一个根底的三角形制作来带大家了解OpenGL ES的根底知识,作为一个入门文章。
OpenGLES是什么?
OpenGLES是OpenGL的一个子集,也便是OpenGL的一个精简指令集,首要用于嵌入式设备,如手机,平板等,本质上是一个跨编程言语,跨渠道的编程接口标准。注意是标准,或许你简略能够理解为便是一个接口API。
OpenGLES如何作业的?
讲到如何作业,一定要知道OpenGl的图形烘托管线,指将一些根底数据,如极点数据,色彩,纹路等数据作为输入,经过多个改变处理,终究输出到屏幕上一个进程。这个进程大致包括:极点着色器,图元装备,几许着色器,光栅化,片段着色器,测试与混合这六个阶段。
关于咱们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);
之后所有的操作都是针对前面编写的着色器目标了。
链接极点特点
极点着色器其实不止会传入极点数据作为输入,比如对色彩以及法向量等也能够运用极点数组的方法传入。
所以就需求区别当时输入的数据对应的到底是哪一个特点。
一般咱们的极点数据会被解析成下面这种方法:
- 方位数据被储存为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 之间的联系
:
有了以下根底之后,咱们再来制作一个三角形
制作一个三角形
应用层咱们运用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;
}
结合前面给的一些根底理论知识,相信你是能看懂的。 作用:
代码现已上传到github:需求的自行下载。
好了,本篇文章仅仅OpenGLES的入门文章,后续仍是推出其他相关文章。
别的自己整理了一些关于Android开发进阶以及面试的一些指导: 重视小余,回复“材料”免费获取。