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_Position和a_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值。

相关推荐
刃若新发1 天前
Windows 用本地 MP4 文件搭建 RTSP/HTTP-FLV 播放环境指引
音视频开发
落魄Android在线炒饭3 天前
Android 实战问题反推 OpenGL 概念(上)
android·opengl
扶尔魔ocy10 天前
【qt openGL入门笔记】windows编译MinGW版本的osgearth(2.10.1)
qt·opengl·osg
屏什么录10 天前
录屏超过两小时就卡顿崩盘?从参数调优到代码实现,一文彻底搞定长时间录屏
前端·音视频开发
扶尔魔ocy11 天前
【QT OpenGL】知识点编目及传送门
qt·opengl·osg
字节跳动视频云技术团队11 天前
720P 直升 8K,解密 2026 抖音美好奇妙夜巨幕超分黑科技
人工智能·音视频开发
VimorphTech15 天前
文件名是 MP4 就能处理?用 ffprobe 检查视频流、音轨和时长
java·音视频开发
冬奇Lab17 天前
开源项目第242期:HyperFrames —— 写 HTML 就能渲染视频,专为 AI Agent 设计的确定性视频框架
人工智能·开源·音视频开发
屏什么录22 天前
电脑如何录屏幕视频和声音?从工具到代码,7 种方案帮你彻底搞定
前端·音视频开发
VimorphTech23 天前
转写结果怎么生成双语字幕?TypeScript 实现 SRT、WebVTT 与索引匹配
javascript·音视频开发