OpenGL - 绘制三角形

OpenGL - 绘制三角形

前言

  • 本节目标
    • 掌握VAO,VBO,VEO的概念和用法
    • 在窗口内绘制三角形
  • 重难点都在本节

渲染管线

渲染管线这个名字听起来高端,其实很简单。管线就意味着串行执行,一步步来的代码。说白了就是一个流程图,没有什么并行,架构等复杂的逻辑。借用一张流程图来说:

  • 渲染基础理论:所有的2d,3d物体可以拆分成若干个三角形。
  • 最重要的输入:顶点数据
  • 着重看蓝色部分,因为蓝色部分是开发者可以控制的
  • 顶点着色器,这里着色二字不恰当。其实作用就是点定位代码。
  • 片元着色器,在光栅化后 会转换成像素点,这里的功能就是计算像素最终颜色。这里的计算就非常的精细了

通过上述流程图可知如果我们要实现绘制一个三角形,写顶点着色器就可以了,如果带颜色再加一个片段着色器。

所以首先我们来画一个不带颜色的三角形

绘制三角形

下面是示例是绘制两个三角形组成的正方形,整体思路如下图

  • 上图展示了一个2d/3d对象的生成步骤
  • 数据源生成是一个难点,不可能人工输入,这个可以交给某些数据源生成器,比如Unity直观地拖拉拽。在本次示例比较简单,直接给出顶点数据
  • 数据源生成VAO比较复杂,需要经过VBO,VAO的辅助
  • 着色器代码编写也是难点,这个后续章节再聊。本节给个简单顶点着色器代码如下
kotlin 复制代码
#version 330 core
layout (location = 0) in vec3 position;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
        gl_Position = vec4(position, 1.0f);
}

给出顶点数据

kotlin 复制代码
GLfloat vertices[] = {
        -0.5f, -0.5f, -0.5f, // 1
        0.5f, -0.5f, -0.5f, // 2
        0.5f,  0.5f, -0.5f, // 3
        0.5f,  0.5f, -0.5f, // 4
        -0.5f,  0.5f, -0.5f, // 5
        -0.5f, -0.5f, -0.5f, // 6
        
};

创建VAO(顶点数组对象)

  • 这个步骤肯定是需要VBO辅助的,可以理解为VBO创建顶点的内存,VAO用于指向。
  • 可能需要VEO辅助,VEO的作用是描述顶点数据的意义

下面介绍下这个三个对象即VAO,VBO,VEO

创建VAO对象

kotlin 复制代码
// 创建VAO对象
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
  • 绘制对象的时候就使用它,VBO, EBO主要是用来辅助内存分配的。内存分配完成后,VAO会指向内存地址。
  • 一个对象对应一个VAO

创建VBO

顶点缓冲对象,用于OpenGL在GPU管理顶点数据的对象,从而提升GPU访问顶点数据的性能。

kotlin 复制代码
GLuint VBO;
// 1. 分配起始地址
glGenBuffers(1, &VBO);
// 2. 绑定其实是指定到GPU
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 3. 分配内存大小
glBufferData(GL_ARRAY_BUFFER, sizeof(DRAW_LIGHT::vertices), DRAW_LIGHT::vertices, GL_STATIC_DRAW);
// 4. 配置内存定义,这里即一次取3个float, 从这个三个中取3个,作为postion = 0 的属性供shader使用
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid *) 0);
// 5. 这一步例行公事,意思是启用顶点属性
glEnableVertexAttribArray(0);
  • 创建VBO的过程就是对内存分配的描述。即用C++/OPENgl 去描述顶点数组每个内存块的意义。
  • 步骤是分配地址 -> 绑定地址 -> 分配数据内存 -> 描述内存如何分配
  • 作用是辅助内存分配。可以思考下为啥不能直接用VAO进行内存分配?
  • VBO是可以复用的,即可以重复利用VBO指针
  • VAO和VBO简单的关系可以如下图

这个比较重要,我们在举一个复杂的例子, 如下

一个复杂的vbo例子

kotlin 复制代码
GLfloat vertices[] = {
        // Positions          // Colors           // Texture Coords
         0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // Top Right
         0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f, // Bottom Right
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, // Bottom Left
        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f  // Top Left 
    };
    
    
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);
    // Color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);
    // TexCoord attribute
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);
  • 我们纵向看glVertexAttribPointer的前两个参数组成了两个数组[0,1,2],[3,3,2]。这两个数组定义了VBO的内存模型。
  • 首先一个顶点的内存容量为8个GLfloat。且内存分配为3,3,2, 如下图
  • 在渲染管线的顶点着色器这一步中,读取GLSL。如下。
kotlin 复制代码
#version 330 core
# 读取 location 0, 即上图蓝色部分,交给position变量
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
        gl_Position = vec4(position, 1.0f);
        ourColor = color;
        // We swap the y-axis by substracing our coordinates from 1. This is done because most images have the top y-axis inversed with OpenGL's top y-axis.
        // TexCoord = texCoord;
        TexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
}

💡 注意上述代码,不是本节使用的着色器。只是为了说明原理用。本章所使用的着色器已在本节开头列出

创建EBO

用于描述顶点数据的意义

kotlin 复制代码
GLfloat vertices[] = {
        -0.5f, -0.5f, -0.5f, // 1
        0.5f, -0.5f, -0.5f, // 2
        0.5f,  0.5f, -0.5f, // 3
        0.5f,  0.5f, -0.5f, // 4
        -0.5f,  0.5f, -0.5f, // 5
        -0.5f, -0.5f, -0.5f, // 6
        
};
GLuint indices[] = { // 注意索引从0开始!
        0, 1, 3, // 第一个三角形
        1, 2, 3  // 第二个三角形
};

glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
  • 上述代码描述了第一个三角形用顶点的 0,1,3作为数据源绘制,第二个三角形用1,2,3作为数据源绘制
  • 如何不用EBO描述,默认就是0,1,2为第一个,3,4,5为第二个
  • 从上面两条容易想到,EBO可以优化顶点的数量,比如一个矩形用默认的方式要写6个点(2 (三角形个数)* 3(三角形顶点数)), 但其实矩形只有四个顶点。这种情况就可以用EBO实现

核心绘制代码

kotlin 复制代码
shader.Use();
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
  • shader是顶点着色器和片元着色器的封装。先忽略
  • glBindVertexArray这里是使用VAO
  • glDrawArrays这里出发渲染管线,先经过顶点着色器绘制顶点,再通过片元着色器给片元上色

Github

相关推荐
OpenTiny社区9 分钟前
把 SearchBox 塞进项目,搜索转化率怒涨 400%?
前端·vue.js·github
编程猪猪侠38 分钟前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞42 分钟前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到111 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构
风清云淡_A2 小时前
【REACT18.x】CRA+TS+ANTD5.X封装自定义的hooks复用业务功能
前端·react.js