[OpengGL]渲染流程和程序流程

这节要说的是Opengles的渲染流程和程序流程,都是一些非常基础的东西,觉得已经熟悉的同学可以自行忽略。
以下是一幅经典的Opengl渲染管线流程图


Opengl渲染管线.png

1.顶点数据

2.基本图元

Opengles中包含三种图元方式,点,线,三角形

3.顶点着色器

包含对顶点数据的处理和转换

4.图元装配

把所有输入的顶点数据作为输入,将所有点配装成指定图元的形状

5.几何着色器

(Opengl特有) 把基本图元形式的顶点的几何作为输入,可以通过产生新顶点构造出新的基本图元来生成其它形状

6.细分着色器

(Opengl特有)可以把基本图元细分为更多的基本图形,创建出更加平滑的视觉效果

7.光栅化

像素化,图形映射到屏幕上网络的像素点,生成提供片段给片元着色器处理,光栅化包含一个剪裁处理,会计算舍弃超出定义视窗外的像素

8.片元着色器

为每一个像素点提供最终的颜色,这里会可以提供纹理题图,如果是3D场景其可以包含3D场景的一些额外数据,例如光线,阴影

9.测试和混合

对每个像素点进行深度测试,Alpha测试并进行颜色混合操作,进一步合成整个视窗每一个像素点最终要显示的颜色和透明度

Opengl渲染管线.png

如果从API的角度来分析,你会发现有更多的操作。

0.顶点缓冲

在输入顶点数据的时候需要线做顶点缓冲,这里可以使用顶点缓冲去对象(VBO),顶点数组对象(VAO)。VBO可以减少IO开销,并使用数组记录每一个快数据对应绑定的类型(顶点位置、法向量),这些数据会存放在GPU上。VAO是使用一个数组来存每一个VBO储存的数据、类型,每次回执时就不需要一个一个传递了。
经过片元着色之后,测试和混合也是分很多种

10.剪裁测试

每一个片元在帧缓冲中对应的位置,若干对应的位置裁剪窗口中则将此片元送入下一个阶段,否则会丢弃此片元,可以在屏幕上指定区域绘制,不在这片区域不进行绘制

11.深度测试和模版测试

深度测试是用片元的深度值和帧缓冲中储存的对应位置的片元的深度值进行比较,深度值小的片元会覆盖或混合深度值大得片元。
模板测试 讲回执区域限定在一定的范围内,一般用于湖面倒影,镜像等场合

12.颜色缓冲和混合

如果程序开启了Alpha混合,则可以根据上一阶段对应的片元和帧缓冲的位置片元进行alpha混合

13.抖动

抖动可以模拟更大的色深,需要自己编写算法实现,通过GL_DITHER来控制

14.帧缓冲

opengles并不是直接在屏幕上进行绘制,是预先在帧缓冲区进行绘制,当绘制完之后再下将绘制的结果交换到屏幕上,因此每绘制新的一帧是都需要清除缓冲区的相关数据,否则会产生不正确的绘制效果。

这些都是基本的渲染流程,接下来说一下程序流程,以Android程序为例
这个之前需要了解一下Android中屏幕显示对Opengles的承载,
SurfaceTexture,TextureView, SurfaceView和GLSurfaceView

值得注意的是Android直接内置了Opengles,并内置了GL10,GL20,GL30的类,封装了Opengles的Android API,当然其中也屏蔽了一些细节,对于真正去理解opengles实现有一定的差距。
初学者很多会选用GLSurfaceView来做实现,例如简单绘制图形是没问题的。但是我们如果深入一点学习,例如滤镜,例如录制播放,还是需要使用SurfaceView来做的,因为SurfaceView可以控制绘制的线程,需要自己定义EGL环境,还有SurfaceTexture绑定,同样这也是初学者使用时的难点。

使用一个GLSurfaceView来显示一个三角形为例,这里就屏蔽了EGL、GLThread和SurfaceTexture使用的细节,重点关注在Opengles中的实现。
GLSurfaceView.Render提供三个回调接口
onSurfaceCreated 纹理窗口创建
onSurfaceChanged 视口大小更改
onDrawFrame 绘制

初始化流程.png

初始化的时候,准备好顶点着色器和片元着色器内容,这里面顶点做色器和片元着色器可以使用字符串读取,也可以使用glsl的shader文件来读取。

 //顶点着色器内容
    char vShaderStr[] =
                    "#version 300 es                        \n"
                    "layout(location = 0)in vec4 vPosition; \n"
                    "void main()                            \n"
                    "{                                      \n"
                    " gl_Position = vPosition;              \n"
                    "}                                      \n";

    //片元着色器内容
    char fShaderStr[] =
                    "#version 300 es                        \n"
                    "precision mediump float;               \n"
                    "out vec4 fragColor;                    \n"
                    "void main()                            \n"
                    "{                                      \n"
                    "   fragColor = vec4(1.0,0.0,0.0,1.0);  \n"
                    "}                                      \n";

加载纹理

GLuint LoadShader(GLenum type,const char *shaderSrc){
    GLuint shader;
    GLint compiled;
    //通过shader类型来获取
    shader = glCreateShader(type);

    if(shader ==0){
        return 0;
    }

    //加载shader资源
    glShaderSource(shader,1,&shaderSrc,NULL);
    //编译shader资源
    glCompileShader(shader);
    //获取shader信息
    glGetShaderiv(shader,GL_COMPILE_STATUS,&compiled);

    //编译不成功,打印日志
    if(!compiled){
        GLint infoLen = 0;
        glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&infoLen);

        if(infoLen >1){
            char *infoLog= (char*)malloc(sizeof(char*) *infoLen);
            glGetShaderInfoLog(shader,infoLen,NULL,infoLog);
            LOGE("Error compiling shader:[%s]",infoLog);
            free(infoLog);
        }
        glDeleteShader(shader);
        return 0;
    }
    //返回编译成功的shader
    return shader;
}

创建纹理空间->加载纹理资源->编译纹理

加载纹理绑定到程序

    GLuint vertexShader;
    GLuint fragmentShader;
    GLuint programObject;
    GLint linked;

    //加载顶点着色器
    vertexShader = LoadShader(GL_VERTEX_SHADER, vShaderStr);
    //加载片元着色器
    fragmentShader = LoadShader(GL_FRAGMENT_SHADER, fShaderStr);

    //创建程序
    programObject = glCreateProgram();
    if (programObject == 0) {
        return;
    }

    //绑定顶点着色器
    glAttachShader(programObject, vertexShader);
    //绑定片元着色器
    glAttachShader(programObject, fragmentShader);

    //关联到程序
    glLinkProgram(programObject);

    //获取程序状态
    glGetProgramiv(programObject, GL_LINK_STATUS, &linked);

    //如果没有链接成功
    if (!linked) {
        GLint infoLen = 0;
        //获取错误日志
        glGetProgramiv(programObject, GL_INFO_LOG_LENGTH, &infoLen);

        //错误日志有内容
        if (infoLen > 1) {
            char *infoLog = (char *) malloc(sizeof(char *) * infoLen);
            //读取对应长度内容
            glGetProgramInfoLog(programObject, infoLen, NULL, infoLog);
            //打印
            LOGE("Error compiling shader:[%s]", infoLog);
            //释放log
            free(infoLog);
        }
        //关闭程序
        glDeleteProgram(programObject);
        return;
    }
    //转为全局地址
    g_programObject = programObject;

    //清屏幕
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);

加载纹理->绑定程序

在GLsurfaceView绘制的时候,调用绘制渲染


基础绘制流程.png
 //顶点
    GLfloat vVertices[] = {
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f
    };

    //清屏
    glViewport(0, 0, g_width, g_height);
    glClear(GL_COLOR_BUFFER_BIT);
    //指定使用程序
    glUseProgram(g_programObject);
    //绑定顶点数据到shader
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
    //允许顶点着色器读取GPU数据
    glEnableVertexAttribArray(0);
    //画三角形
    glDrawArrays(GL_TRIANGLES, 0, 3);

清屏->指定使用程序->传入参数到着色器->允许GPU读取->绘制图形


image.png

日志打印

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

Opengles2.0与Opengles3.0区别

Opengles2.0对应的是Android 2.2(API 8)
Opengles3.0对应的是Android 4.3(API 18)
Opengles3.1对应的是Android 5.0(API 21)

先将顶点着色器和片段着色器文件贴出来(这是用来渲染普通视频用的),这是使用的OpenGLES3.0版本。(存在兼容性问题),下面只是一部分问题,且这里就不将bug的log写出来了,这是提示大家正确的写法。

顶点着色器

#version 300 es
in vec4 aPosition;//顶点位置
in vec2 aTexCoord;//S T 纹理坐标
out vec2 vTexCoord;
void main() {
    vTexCoord = aTexCoord;
    gl_Position = aPosition;
}

片段着色器

#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
in vec2 vTexCoord;
uniform samplerExternalOES sTexture;
out vec4 vFragColor;
void main() {
    vFragColor=texture(sTexture, vTexCoord);
}

1.没有在着色器文件中标明使用版本的时候默认使用2.0版本。
在上面的着色器文件中添加#version 300 es即表明使用3.0版本,如果不添加则使用默认2.0版本(注意此行必须放在第一行)。同时注意使用3.0的api的时候必须添加此行。

2.3.0中attribute变成了in和out
OpenGL ES 3.0中将2.0的attribute改成了in,顶点着色器的varying改成out,片段着色器的varying改成了in,也就是说顶点着色器的输出就是片段着色器的输入,另外uniform跟2.0用法一样。

3.3.0中使用内置参数gl_FragColor会出问题
这里我们只能自定义颜色向量out vec4 vFragColor;

4.3.0中将GL_OES_EGL_image_external变为了GL_OES_EGL_image_external_essl3
在使用纹理扩展的时候,也就是uniform samplerExternalOES sTexture的时候。在3.0中我们使用GL_OES_EGL_image_external_essl3而不是GL_OES_EGL_image_external。使用相机采集纹理的时候就知道了

5.3.0中将纹理的处理方法统一为texture
在2.0中2D纹理和3D纹理处理分别使用texture2D和texture3D方法,而在3.0后使用texture统一处理。

6.in或者out变量等不能在函数内(如main函数内)声明

OpenglES3.0新特性

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容