前言
随着通信技术的快速发展,以及视频业务的激烈扩张。作为研发人员,对音视频这块的知识还是需要知道一些的。渲染作为其中的一个重要组成部分,本系列就用通俗易懂的语言,来介绍opengl。
OpenGL 是啥?
opengl 全名是open graphics library , 用于渲染2d,3d图像的跨平台,跨语言的应用程序编程接口。
OpenGL 能做什么?
opengl能做的事情有很多,比如可以对图像进行各种美颜,滤镜,裁剪,贴纸等处理,源图像数据可以是来自相机,文件,图片等。像业内有名的GPUImage就是用opengl来做的,可以体验一下。
小试牛刀
废话少说,干就完事了,在实际项目体验中来加深对opengl的理解吧。
我们用android开发环境来体验opengl的渲染能力。本次的需求是使用opengl的能力在手机屏幕上画一个三角形。
想想之前如果有这样的需求,android开发应该怎么做呢? 应该是要问设计要一张三角形的图片资源,然后直接放imageview上面展示出来,或者就自定义一个view,然后自己draw出来吧。无论怎么做,布局文件上都需要放一个view控件。
那么这次我们也放一个控件把,不过放的不是普通的view,而是GLSurfaceview。还记得原来工作那会,虽然知道有GLSurfaceview这个东西,但是从来没用过,也不知道能干啥。现在才知道,它是专门给opengl配合用的。
- 布局文件添加,GLSurfaceview。
- 自定义Render。
- GLSurfaceview设置Render。
自定义render继承自 GLSurfaceview内的IRender接口。IRender接口有三个回调方法,这个三个回调方法比较重要一点的是,这三个回调方法都属于GL线程。这是关键。
public class MyRender implements GLSurfaceView.Renderer {
private static final String TAG = "MyRender";
private final Context context;
private Triangle triangle;
public MyRender(Context context) {
this.context = context;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.d(TAG, "onSurfaceCreated: ");
String vertexSource = FileUtils.readTextFromRawResource(context, "base_vertex.glsl");
String fragmentSource = FileUtils.readTextFromRawResource(context, "base_fragment.glsl");
int vertexShaderId = GlUtils.createAndCompileVertexShader(vertexSource);
int fragmentShaderId = GlUtils.createAndCompileFragmentShader(fragmentSource);
Log.d(TAG, "onSurfaceCreated: vertexShaderId = "+vertexShaderId+"--fragmentShaderId = "+fragmentShaderId);
int program = GlUtils.createProgram(vertexShaderId, fragmentShaderId);
triangle = new Triangle(program);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.d(TAG, "onSurfaceChanged: width = "+width+"--height = "+height);
GLES20.glClearColor(1f, 0f, 0f, 1f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
triangle.draw();
}
}
根据上面的自定义的render,分析一波。在onSurfaceCreate回调中。
- 先是拿到了顶点着色器和片元着色器的内容GLSL(Open GL Shader Language)。(PS: 相关名词不懂没关系,后面系列的文章会介绍,这里现有概念即可)
- 然后根据他两的内容,分别创建了对应的着色器id,这里可以理解为着色器的引用,我们就可以通过id开访问对应的着色器。
- 然后跟据顶点着色器和片元着色器创建了一个program(程序)。通过program id来访问。
onSurfaceChanged回调中,拿到了view 的宽和高的信息,通过这个信息,主要调用了glviewport 函数来设置视口的大小,这里可以把视口理解为绘制区域。0,0 代表区域左上角的位置,width,height就是区域的宽高。 至于那两个clear函数,就理解清理颜色缓存的操作吧。
onDrawFrame 顾名思义就是画内容咯,这里我们就打算把画三角形的操作放这里。
在上面我们看到Triangle, 本着面向对象的思想,Triangle里面必然都是三角形相关的信息了。
public class Triangle {
private static final String VERTEX_COOR_LABEL = "vCoordinate";
private static final String COLOR_LABEL = "aColor";
private final int program;
private static final float[] sVertex = {
0f, 0.5f,
-0.5f, 0f,
0.5f, 0f
};
private static final float[] sColor = {
1.0f, 0f, 0f,
0f, 1.0f, 0f,
0f, 0f, 1.0f
};
private final int vertexCoorHandle;
private final int aColorHandle;
private final FloatBuffer vertexCoorBuffer;
private final FloatBuffer colorBuffer;
public Triangle(int program) {
this.program = program;
vertexCoorHandle = GLES20.glGetAttribLocation(program, VERTEX_COOR_LABEL);
aColorHandle = GLES20.glGetAttribLocation(program, COLOR_LABEL);
vertexCoorBuffer = ByteBuffer.allocateDirect(2 * 3 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexCoorBuffer.clear();
vertexCoorBuffer.put(sVertex);
colorBuffer = ByteBuffer.allocateDirect(3 * 3 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
colorBuffer.clear();
colorBuffer.put(sColor);
}
public void draw() {
GLES20.glUseProgram(program);
vertexCoorBuffer.position(0);
GLES20.glEnableVertexAttribArray(vertexCoorHandle);
GLES20.glVertexAttribPointer(vertexCoorHandle, 2, GLES20.GL_FLOAT, false, 0, vertexCoorBuffer);
colorBuffer.position(0);
GLES20.glEnableVertexAttribArray(aColorHandle);
GLES20.glVertexAttribPointer(aColorHandle, 3, GLES20.GL_FLOAT, false, 0, colorBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
GLES20.glDisableVertexAttribArray(vertexCoorHandle);
GLES20.glDisableVertexAttribArray(aColorHandle);
}
}
卧槽这一堆,gles开空的函数调用好多啊,完全不能理解具体用法啊?
看到这,不要慌,后面的会慢慢介绍的。哪能一下子啥都知道啊?(ps:我一下也说不明白。。。。)
总的来说,这个类就是,对三角形的描述,比如:sVertex float数组来描述三角形的三个顶点的位置,此时聪明的你会想到,sVertex这个数组有6个元素,三角形3个顶点的话,那就是每个顶点2个元素,正好是x,y; 平面直角坐标系的点的坐标值。但是这个数值怎么这么小呢,才0,0.5f, 可取的范围是多少呢?
答案是:顶点可取的范围是-1,1; 同时这里的坐标系是opengl坐标系。具体介绍,看后续。本文不解释。
sColor 数组描述每个顶点的颜色,rgb表示,所以这里是三个元素对应一个顶点。
在构造函数里,虽然不知道函数具体作用,但是可以根据名称猜出来它的功能(ps: 所以写程序的过程还是要做好命名工作,好的命名让别人看的舒服啊)
构造函数里通过GLES20.glGetAttribLocation 函数,拿到索引,然后把顶点位置,颜色信息,塞进去。
把信息塞进去的代码如下:
`
vertexCoorBuffer.position(0);
GLES20.glEnableVertexAttribArray(vertexCoorHandle);
GLES20.glVertexAttribPointer(vertexCoorHandle, 2, GLES20.GL_FLOAT, false, 0, vertexCoorBuffer);
colorBuffer.position(0);
GLES20.glEnableVertexAttribArray(aColorHandle);
GLES20.glVertexAttribPointer(aColorHandle, 3, GLES20.GL_FLOAT, false, 0, colorBuffer);
`
然后再调用 GLES20.glDrawArrays 来画图像。
最后看下入口的代码:
public class MainActivity extends AppCompatActivity {
private GLSurfaceView glSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
glSurfaceView = findViewById(R.id.content_view);
glSurfaceView.setEGLContextClientVersion(2);
glSurfaceView.setRenderer(new MyRender(this));
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
}
然后我们看下运行效果:
总结
回顾下做了什么吧。
- 利用GLSL语法,创建了顶点着色器和片元着色器。(这都说的啥?)
- 利用这两个着色器创建了program程序。(program??)
- 通过program访问到了三角形属性的句柄,因而我们可以把我们定义的属性值赋值进去。(勉强听懂)
- 使用glviewport 指定渲染的区域,因为GLSurfaceview设置的是全屏,所以指定渲染区域也是全屏。(背景颜色为啥是红色?)
- draw时候,把颜色位置属性设置进去,调用了glDrawArrays, 把三角形画了出来。(假装听懂。。)
嗯,经过复盘回顾,对细节处可能不清除,但是大致的流程应该是知道了,后面的文章,会再逐个介绍不懂的地方。