阅读 61

白话OpenGL系列(一)一个小demo

白话OpenGL系列(二)GLSL介绍

前言

随着通信技术的快速发展,以及视频业务的激烈扩张。作为研发人员,对音视频这块的知识还是需要知道一些的。渲染作为其中的一个重要组成部分,本系列就用通俗易懂的语言,来介绍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配合用的。

  1. 布局文件添加,GLSurfaceview。
  2. 自定义Render。
  3. 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回调中。

  1. 先是拿到了顶点着色器片元着色器的内容GLSL(Open GL Shader Language)。(PS: 相关名词不懂没关系,后面系列的文章会介绍,这里现有概念即可)
  2. 然后根据他两的内容,分别创建了对应的着色器id,这里可以理解为着色器的引用,我们就可以通过id开访问对应的着色器。
  3. 然后跟据顶点着色器和片元着色器创建了一个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);
    }


}
复制代码

然后我们看下运行效果:

在这里插入图片描述

总结

回顾下做了什么吧。

  1. 利用GLSL语法,创建了顶点着色器和片元着色器。(这都说的啥?)
  2. 利用这两个着色器创建了program程序。(program??)
  3. 通过program访问到了三角形属性的句柄,因而我们可以把我们定义的属性值赋值进去。(勉强听懂)
  4. 使用glviewport 指定渲染的区域,因为GLSurfaceview设置的是全屏,所以指定渲染区域也是全屏。(背景颜色为啥是红色?)
  5. draw时候,把颜色位置属性设置进去,调用了glDrawArrays, 把三角形画了出来。(假装听懂。。)

嗯,经过复盘回顾,对细节处可能不清除,但是大致的流程应该是知道了,后面的文章,会再逐个介绍不懂的地方。

白话OpenGL系列(二)GLSL介绍


欢迎订阅我的公众号关注我哈

在这里插入图片描述

文章分类
代码人生
文章标签