首页 > 其他分享 >02 绘制简单几何图形

02 绘制简单几何图形

时间:2023-04-19 19:45:48浏览次数:50  
标签:02 几何图形 渲染 绑定 顶点 GL 绘制 着色器 VAO

图形渲染管线与绘制简单几何图形

1. 图形渲染管线回顾

简要回顾一下GAMES101中闫老师提到的图形渲染管线。

img

图形渲染管线可以理解为,将原始的3维图形数据经过一系列变化处理后,转换为2维坐标,再将2维坐标转换为实际的屏幕像素的过程。

这一过程可以简单的描述为:

  1. 首先我们要做的是输入一系列三维空间顶点;
  2. 接着对每一个顶点做model,view,projection变换,把三维空间中的点投影在二维屏幕上。
  3. 其次按照我们需要绘制的几何信息,将点构成三角形。
  4. 然后我们进行光栅化。通过判断屏幕空间上每一个像素中心点是否在三角形内,把三角形离散为不同的像素。当光栅化产生一系列的fragment/像素时,判定其是否可见。
  5. 之后我们对像素进行着色。这一着色过程可以发生在Vertex Processing(Gouraud Shading,每个顶点做一次着色)和Fragment Processing(Phong shading,每像素做一次着色)两个阶段 。
  6. 最后输出屏幕图像。

图形渲染管线的每个阶段会把前一个阶段的输出作为输入,并且可以在GPU上并行执行,快速处理数据。

2. 渲染三角形

想要绘制一个三角形,除了设置顶点数据,将顶点数据初始化至缓冲,之外我们还要构建并编译我们的着色器程序。在这里,我们分别定义一个顶点着色器和片段着色器,然后我们把两个着色器对象链接到一个用来渲染的着色器程序中,并在渲染前告诉OpenGL该如何解析顶点数据。渲染时我们使用定义的顶点属性配置来绘制图元。

我们首先构建编译着色器,接着再传入初始数据绘制三角形。

2.1 构建编译着色器程序

2.1.1 顶点着色器

//暂时将顶点着色器的源代码硬编码在文件顶部的C风格字符串中,为了能够让OpenGL使用它,必须在运行时动态编译它的源代码。
const char *vertexShaderSource = "#version 330 core\n"   //版本声明,核心模式。
    "layout (location = 0) in vec3 aPos;\n"//在顶点着色器中声明所有的输入顶点属性,这里只设置3D坐标。layout (location = 0)设定了输入变量的位置值。
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"//将位置数据赋值给预定义的gl_Position变量,并作为顶点着色器的输出。
    "}\0";

int main(){
......

    //1.顶点着色器

    unsigned int vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建着色器对象,着色器类型为GL_VERTEX_SHADER
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//glShaderSource把着色器源码附加到着色器对象上。参数分别为:着色器对象,源码字符串数量,着色器源码,NULL(暂定)
    glCompileShader(vertexShader);//编译

    //2.检查着色器编译是否错误

    int  success;//定义一个整型变量表示是否成功编译
    char infoLog[512];//定义一个储存错误消息的容器
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);//glGetShaderiv检查是否成功编译
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);//获取错误信息并打印
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
......
}

2.1.2 片段着色器

计算像素最后的颜色

const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n" //用out来声明输出变量
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"//将一个Alpha为1的颜色赋值给颜色输出。
"}\n\0";

int main(){
......
    //3.片段着色器
    unsigned int fragmentShader;
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    //4.检查着色器编译错误
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
......
}

2.1.3 着色器程序

顶点着色器和片段着色器都已经编译完毕,接下来我们必须把两个着色器对象链接到一个用来渲染的着色器程序对象(Shader Program Object)中。在渲染对象的时候激活这个着色器程序,已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。

int main(){
......
    //5.链接着色器
    unsigned int shaderProgram;
    shaderProgram = glCreateProgram();//创建一个着色器程序对象,并返回新创建程序对象的ID引用。
    glAttachShader(shaderProgram, vertexShader);//把顶点着色器附加到着色器程序对象上
    glAttachShader(shaderProgram, fragmentS);
    glLinkProgram(shaderProgram);//链接两个着色器
    //6.检查链接着色器程序是否错误,并获得相应的日志
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        ...
    }
   
    //着色器对象链接到程序对象之后,删除着色器对象
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

}

在渲染循环中,我们可以调用glUseProgram函数,激活这个着色器程序对象:

int main(){
......

while (!glfwWindowShouldClose(window))
{
......
glUseProgram(shaderProgram);//glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用之前写的着色器了。
}
}

2.2 顶点输入

本文的目的是渲染两个相邻的三角形。所以我们在这里传入6个3D坐标作为图形渲染管线的输入。这6个顶点使用数组的形式传入,该数组我们称之为顶点数据(Vertex Data),而顶点数据又是用顶点属性(Vertex Attribute)表示的,它可以包含任何我们想用的数据,在这里为了简便起点,顶点数据只由3D位置和RGB值组成。

OpenGL仅当3D坐标在3个轴(x、y和z)上(-1.0,1.0)的范围内时才处理它。所有在这个范围内的坐标叫做标准化设备坐标(Normalized Device Coordinates),此范围内的坐标最终显示在屏幕上(在这个范围以外的坐标则不会显示)。

定义一个标准化设备坐标的float数组,然后将这个数组发送给顶点着色器:

float vertices[] = {
        // 第一个三角形
        -0.9f, -0.5f, 0.0f,  // 左
        -0.0f, -0.5f, 0.0f,  // 右
        -0.45f, 0.5f, 0.0f,  // 上 
        // 第二个三角形
         0.0f, -0.5f, 0.0f,  // 左
         0.9f, -0.5f, 0.0f,  // 右
         0.45f, 0.5f, 0.0f   // 上 
    };

顶点着色器会在GPU上创建内存用于储存我们的顶点数据,我们通过顶点缓冲对象(VBO)来管理这个内存。

在渲染前,我们需要指定OpenGL该如何解释顶点数据,也即是链接顶点属性,这通过设置顶点属性指针来完成。

    unsigned int VBO;
    glGenBuffers(1, &VBO);//创建顶点缓冲对象VBO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//利用glBindBuffer函数把新创建的VBO绑定到GL_ARRAY_BUFFER目标上。

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把用户定义的数据复制到当前绑定缓冲的函数 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //第一个参数指定我们要配置的顶点属性,第二个参数指定顶点属性的大小,第三个参数指定数据的类型,第四个参数决定数据是否被标准化,第五个参数为步长,第六为强制类型转换。
    glEnableVertexAttribArray(0);//以顶点属性位置值为参数,启用顶点属性。
    

当绘制更多的物体,或者物体具有更多的顶点属性时,绑定正确的缓冲对象,为每个物体配置所有顶点属性就会变得极其复杂。我们使用顶点数组对象(VAO)来解决这个困难。

VAO可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO即可。

生成/配置/解绑所有的VAO,VBO代码如下:

    unsigned int VBO;
    unsigned int VAO;
    glGenBuffers(1, &VBO);//创建顶点缓冲对象VBO
    glGenVertexArrays(1, &VAO);//创建顶点数组对象VAO
    glBindVertexArray(VAO);//首先绑定VAO,然后绑定并设置顶点缓冲,配置顶点属性

    //绑定顶点缓冲
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//利用glBindBuffer函数把新创建的VBO绑定到GL_ARRAY_BUFFER目标上。
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把用户定义的数据复制到当前绑定缓冲的函数

    //配置顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//该函数告知OpenGL该如何解析顶点数据。第一个参数指定我们要配置的顶点属性,第二个参数指定顶点属性的大小,第三个参数指定数据的类型,第四个参数决定数据是否被标准化,第五个参数为步长,第六为强制类型转换。
    glEnableVertexAttribArray(0);//以顶点属性位置值为参数,启用顶点属性。

    //解绑VAO,VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);//对glVertexAttribPointer的调用将VB哦注册为顶点属性的绑定顶点缓冲区对象,因此之后我们可以安全的解除绑定
    glBindVertexArray(0);//之后,你可以取消绑定VAO,这样其他VAO调用就不会意外修改此VAO,但这情况很少发生。修改其他VAO无论如何都要调用glBIndVertexArray,所以若无必要,通常不解绑VAO,VBO。

2.3 绘制图形

        //绘制我们的第一个三角形
       
        glUseProgram(shaderProgram);//激活着色器程序对象。glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用之前写的着色器了。
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);//GL_TRIANGLES为OpenGL图元类型,0为顶点数组起始索引,6为绘制六个顶点
        glBindVertexArray(0); //可以注释掉,不需要每次解除绑定

img

3. 渲染矩形

当我们在绘制矩形的时候,我们实际上再绘制两个拥有同一个边的三角形。

矩形有四个顶点,两个三角形确是六个顶点,多出的顶点无疑会造成额外的计算开销。

为了解决这个问题,OpenGL使用索引绘制的方案来储存不同的顶点,并设定绘制这些顶点的顺序。

具体来说,我们首先只定义两个三角形的四个不同顶点,然后创建元素缓冲对象(EBO),这个缓冲区可以存储 OpenGL 用来决定要绘制哪些顶点的索引。通过调用索引缓冲区,我们来绘制两个三角形,达到绘制矩形的目的。

 //设置顶点数据(和缓冲区)并配置顶点属性
    float vertices[] = {
        0.5f,  0.5f, 0.0f, //右上
        0.5f, -0.5f, 0.0f, //右下
       -0.5f, -0.5f, 0.0f, //左下
       -0.5f,  0.5f, 0.0f  //左上
    };

    unsigned int indices[] = {
        0,1,3,//索引从0开始
        1,2,3
    };
 
    ......
    unsigned int EBO;
    glGenBuffers(1, &EBO);//创建元素缓冲对象

    ......

    //绑定元素缓冲对象
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //绑定EBO
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//把索引复制到缓冲中。
    

    //解绑EBO
   glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);//VAO处于活跃状态时,不要解除EBO的绑定,因为绑定的元素缓冲区对象存储在VAO中。


 //渲染循环
    while (!glfwWindowShouldClose(window))
    {
        ......
       
        glUseProgram(shaderProgram);//激活着色器程序对象。glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用之前写的着色器了。
        glBindVertexArray(VAO);

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//绘制模式,顶点个数,索引类型,EBO偏移量
    
        glBindVertexArray(0); //可以注释掉,不需每次解除绑定
        
    }

img

参考资料:

1.GAMES101-现代计算机图形学入门-闫令琪_哔哩哔哩_bilibili

2.LearnOpenGL CN

3.Anton's OpenGL 4 Tutorials0

标签:02,几何图形,渲染,绑定,顶点,GL,绘制,着色器,VAO
From: https://www.cnblogs.com/nexus-helix/p/17334396.html

相关文章

  • 2023/4/19
    今日站立会议,对任务进度进行报告,规划下一步如何进行,如何进行供货商数据库的对接,如何对数据进行处理。如何显示数据。 ......
  • 2023年4月19日周三
    计划找杨哥问邮件发送的问题研究如何实现权限控制新增修改接口的,这属于下周的审核权限了继续读懂代码,补充相关知识收集免费接口执行09点12分  继续看完英语那个10点02分  花钱买了还是,每天早上端电脑学吧,开始搞毕设11点32分  看了半天学校,16点42分  解决mo......
  • papamelon 302. 碰撞游戏 Stripies(挑战程序设计竞赛)
    地址https://www.papamelon.com/problem/302http://poj.org/problem?id=1862解答自取了几个样例从大到小和从小到大进行模拟发现最大的数最先碰撞则开方的次数最多,所以需要结果最小,则优先去最大的数进行合并。代码如下#include<iostream>#include<algorithm>#includ......
  • C/C++哈希表应用[2023-04-19]
    C/C++哈希表应用[2023-04-19]选题二十:哈希表应用[问题描述]利用哈希表进行存储任务要求:针对一组数据进行初始化哈希表,可以进行显示哈希表,查找元素,插入元素,删除元素退出程序操作。设计思想:哈希函数用除留余数法构造,用线性探测再散列处理冲突。设计目的:实现哈希表的综合操......
  • 数据结构与算法学习02
    学习要点一、复习内容重点及难点:算法的时间复杂度分析。过程:(1)找寻循环中的最内层语句(2)分析该语句的执行次数。二、预习内容1、预习页码:21-22,30,32-422、具体预习内容(1)算法的平均时间复杂度分析。不能用最好和最坏的极端情况分析算法,应该用等概率的平均时间复杂度来进行分析......
  • 2023年windows DockerDeskTop最新款4.18.0 全程保姆级安装
    目录前景提示windows10内置的linux系统1.这个内置系统一定要在windowsstore里安装,否则,无法使用,这是重点。进入商店,搜索linux。2.一般画圈这些都可以使用。4.安装会让你输入微软账户密码(首次)。5.静静等待,本作的这个大概550M左右。6.装好后,会生成一个图标(像应用程序一样,双击......
  • 20201306 Exp5 信息搜集与漏洞扫描
    目录一、实践目标及实践内容实践目标实践内容二、实践原理三、实践过程记录1、各种搜索技巧的应用搜索网址目录结构利用搜索引擎搜索特定类型的文件使用traceroute命令进行路由侦查2、DNSIP注册信息的查询whois查询nslookup查询dig查询LP2Location地理位置查询IP......
  • 2023 年十大 API 管理趋势
    本文探讨了API管理在数字化转型中的重要性,以及API管理面临的挑战和发展机遇。文章重点介绍了十大API管理发展趋势,包括API安全性、API标准化、云端API管理解决方案、低代码API平台、API市场、新兴API协议、人工智能与API、开发者体验、API分析和无服务器架构等。......
  • Apache Tomcat拒绝服务漏洞 CVE-2022-29885
    【预警类型】中危预警【预警内容】ApacheTomcat拒绝服务漏洞 CVE-2022-29885漏洞编号:CVE-2022-29885一、漏洞概述2022年7月2日,安全团队监测到一则ApacheTomcat 拒绝服务漏洞的信息。该漏洞是由于Tomcat开启集群配置中存在缺陷,攻击者可利用该漏洞在未权限的情况下,构造恶意......
  • 江苏坤和鑫电子科技有限公司2023年第一季度销量猛增
    江苏坤和鑫电子科技有限公司2023年第一季度营销收入比往年增长,在杨总带领下,该公司全体员工,心往一处想,劲往一处使,首度开门红,公司成立于2014年,是一家专业加工销售进口/国产精密不锈钢带的企业。公司目前占地面积2000M,年销售材料1000吨以上,主营产品于,日本,南韩马来西亚,欧洲等国家的精密......