OpenGL深入浅出(2)—渲染流程1

之前的文章:OpenGL深入浅出(1)---导读,我们简述了下OpenGL的基础知识,本篇文章我们从一个OpenGL绘制四边形的demo出发,给大家讲解下openGL的渲染流程。

1、概述

图形渲染管线的执行顺序是固定的,可以简单概括为:顶点着色器->图元装配->光栅化->片段着色器->像素操作,参考下图:

对照着上图,OpenGL的渲染流程为:

  1. 顶点着色器:图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
  2. 图元装配:将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状
  3. 几何着色器:图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。
  4. 光栅化:几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
  5. 片段着色器:片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
  6. 测试和混合:在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段我们在后续的文章中展开讲。

2、实例代码

刚刚我们讲完了OpenGL的渲染流程,我们用一个绘制四边形贴图的例子来讲解下上述流程,对源码感兴趣的同学可以点击这里查看源码

注意图形渲染管线的流程是在执行glDrawArrays的时候,走上图的流程。接下来我们来一个绘制渲染四边形的实例代码浅浅分析下流程

cpp 复制代码
#include <jni.h>
#include <GLES3/gl3.h>

#include <android/log.h>
#include <stdbool.h>
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "CommonUtil.h"

static GLuint program;
static GLuint positionHandle;
static GLuint colorHandle;
static GLuint texCoordHandle;
static GLuint textureId;
static int width, height, channels;
static unsigned char *imageData;

//
// Created by ryan on 2023/8/23.
//


void rect_nativeInit() {
    const char *vertexShaderCode =
            "#version 300 es\n"
            "layout(location = 0) in vec4 a_Position;\n"
            "layout(location = 1) in vec4 a_Color;\n"
            "layout (location = 2) in vec2 a_TexCoord;\n"
            "out vec4 v_Color;\n"
            "out vec2 v_TexCoord;\n"
            "uniform mat4 model;\n"
            "uniform mat4 view;\n"
            "uniform mat4 projection;\n"
            "void main() {\n"
            "  v_Color = a_Color;\n"
            "  gl_Position = projection * view * model * a_Position;\n"
            //            "  v_TexCoord = vec2(a_TexCoord.x, 1.0 - a_TexCoord.y);\n"
            "  v_TexCoord = a_TexCoord;\n"
            "}\n";

    const char *fragmentShaderCode =
            "#version 300 es\n"
            "precision mediump float;\n"
            "in vec4 v_Color;\n"
            "in vec2 v_TexCoord;\n"
            "out vec4 o_Color;\n"
            "uniform sampler2D ourTexture;\n"
            "void main() {\n"
            "  o_Color = texture(ourTexture, v_TexCoord);\n"
            "}\n";

    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderCode, NULL);
    glCompileShader(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderCode, NULL);
    glCompileShader(fragmentShader);

    program = glCreateProgram();
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    glLinkProgram(program);

    positionHandle = glGetAttribLocation(program, "a_Position");
    colorHandle = glGetAttribLocation(program, "a_Color");
    texCoordHandle = glGetAttribLocation(program, "a_TexCoord");
    stbi_set_flip_vertically_on_load(true);
    imageData = stbi_load("/sdcard/Pictures/monkey.png", &width, &height, &channels,
                          STBI_rgb_alpha);

// 创建纹理对象,并设置纹理参数
    glGenTextures(1, &textureId); // 生成一个纹理单元
    glBindTexture(GL_TEXTURE_2D, textureId); // 绑定纹理
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

// 将图片数据上传到纹理对象中
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 imageData); // 利用imageData生成纹理对象
// 以上为生成纹理的代码
}

void rect_nativeRender() {
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    GLfloat vertices[] = {
            -0.5f, 0.5f, 0.0f, 1.0f,
            0.5f, 0.5f, 0.0f, 1.0f,
            -0.5f, -0.5f, 0.0f, 1.0f,
            0.5f, -0.5f, 0.0f, 1.0f
    };

    GLfloat colors[] = {
            1.0f, 0.0f, 0.0f, 1.0f,
            0.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f
    };

    GLfloat texCoords[] = {
            0.0f, 1.0f,
            1.0f, 1.0f,
            0.0f, 0.0f,
            1.0f, 0.0f,
    };

    glUseProgram(program);

    glVertexAttribPointer(positionHandle, 4, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(positionHandle);

    glVertexAttribPointer(colorHandle, 4, GL_FLOAT, GL_FALSE, 0, colors);
    glEnableVertexAttribArray(colorHandle);

    glVertexAttribPointer(texCoordHandle, 2, GL_FLOAT, GL_FALSE, 0, texCoords);
    glEnableVertexAttribArray(texCoordHandle);

//    glm::mat4 trans =  glm::mat4(1.0f);
//    trans = glm::rotate(trans, glm::radians(-55.0f), glm::vec3(1.0, 0.0, 0.0));
//    trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
//    LOGW("imageData, %d, %d, transformLoc: %d", width, height, transformLoc);

    glm::mat4 model = glm::mat4(1.0f);
    model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
    glm::mat4 view = glm::mat4(1.0f);
// 注意,我们将矩阵向我们要进行移动场景的反方向移动。
    view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
    glm::mat4 projection = glm::mat4(1.0f);
    projection = glm::perspective(glm::radians(45.0f), 1.0f, 0.1f, 100.0f);

    glGetError();
    GLint modelLoc = glGetUniformLocation(program, "model");
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
    GLint viewLoc = glGetUniformLocation(program, "view");
    glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
    GLint projectionLoc = glGetUniformLocation(program, "projection");
    glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
    LOGW("glGetError, %d", glGetError());
    glDrawArrays(GL_TRIANGLE_STRIP, 0 , 4);
    LOGW("glGetError end, %d", glGetError());
}

3、glDrawArrays执行以后会走的流程

先前我们绑定了顶点属性(其实也就是声明为layout的属性),所以在绘制时会:

  1. 步骤一:解析顶点数据成多个顶点着色器输入参数结构体------解析顶点数据(即vertices和colors)到顶点数据(a_Positiona_Color)数组
  2. 步骤二:遍历入参结构体,依次执行顶点着色器自定义函数,函数main内gl_Position设置位置;设置当前顶点颜色值给片段着色器
  3. 步骤三:遍历结束以后,所有输出会存储在顶点缓冲区(Vertex Buffer Object即VBO)中
  4. 步骤四:片段着色器从参数里拿到颜色值,并执行main逻辑

步骤一中我们提到了解析顶点属性,我们会发现上述例子中定义了这样一个数组vertices,其实就是坐标属性

cpp 复制代码
    GLfloat vertices[] = {
            -0.5f, 0.5f, 0.0f, 1.0f,
            0.5f, 0.5f, 0.0f, 1.0f,
            -0.5f, -0.5f, 0.0f, 1.0f,
            0.5f, -0.5f, 0.0f, 1.0f
    };

那么OpenGL环境是怎么解析这个数组变成一组坐标的呢,是在 glVertexAttribPointer(positionHandle, 4, GL_FLOAT, GL_FALSE, 0, vertices);这行代码其执行了顶点数组为4个一组的float类型,也就代表着vertices会被解析成4个坐标:(-0.5f, 0.5f, 0.0f, 1.0f)、(0.5f, 0.5f, 0.0f, 1.0f)、(-0.5f, -0.5f, 0.0f, 1.0f)、(0.5f, -0.5f, 0.0f, 1.0f)。

同样的我们会发现还有一个colors数组,它也有自己的解析方法:glVertexAttribPointer(colorHandle, 4, GL_FLOAT, GL_FALSE, 0, colors);,它也是四个一组的float类型。其源码:

cpp 复制代码
    GLfloat colors[] = {
            1.0f, 0.0f, 0.0f, 1.0f,
            0.0f, 1.0f, 0.0f, 1.0f,
            0.0f, 0.0f, 1.0f, 1.0f,
            1.0f, 1.0f, 0.0f, 1.0f
    };

glVertexAttribPointer在解析的时候,解析出来就不是坐标了,而是颜色,四个float为一组,其顺序对应了颜色:红、绿、蓝和alpha值。

相关推荐
dvlinker2 天前
【音视频开发】使用支持硬件加速的D3D11绘图遇到的绘图失败与绘图崩溃问题的记录与总结
音视频开发·c/c++·视频播放·d3d11·d3d11绘图模式
起司锅仔3 天前
OpenGL ES 着色器(5)
android·安卓·opengl·着色器
起司锅仔3 天前
OpenGL ES MVP/变换投影矩阵(8)
android·安卓·opengl
起司锅仔5 天前
OpenGL ES 之EGL(6)
android·安卓·opengl
起司锅仔5 天前
OpenGL ES 纹理(7)
android·安卓·opengl
音视频牛哥7 天前
Android平台GB28181实时回传流程和技术实现
音视频开发·视频编码·直播
rainInSunny7 天前
Learn OpenGL In Qt之炫酷进度条
c++·qt·opengl
起司锅仔8 天前
OpenGL ES 绘制一个三角形(2)
android·安卓·opengl
音视频牛哥8 天前
RTMP、RTSP直播播放器的低延迟设计探讨
音视频开发·视频编码·直播