Learn OpenGL 02 你好,三角形

图形渲染管线

图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分

首先,我们以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据(Vertex Data)。

顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。

图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状;本节例子中是一个三角形。

图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。

几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。

在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。

顶点输入

 float vertices[] = {
        -0.5f, -0.5f, 0.0f,
         0.5f, -0.5f, 0.0f,
         0.0f,  0.5f, 0.0f
            };
        unsigned int VBO;
        glGenBuffers(1, &VBO);//生成一个VBO缓冲对象
        glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定缓冲对象
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把之前定义的顶点数据复制到缓冲的内存中
        //glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
        // 它的第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。
        // 第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。
        // 第三个参数是我们希望发送的实际数据。
        // 第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
        //    GL_STATIC_DRAW :数据不会或几乎不会改变。
        //    GL_DYNAMIC_DRAW:数据会被改变很多。
        //    GL_STREAM_DRAW :数据每次绘制时都会改变。

着色器

        unsigned int vertexShader;//创建一个着色器对象,用id来引用
        vertexShader = glCreateShader(GL_VERTEX_SHADER);//创建着色器
        glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//着色器源码附加到着色器对象上
        glCompileShader(vertexShader);//编译着色器

        unsigned int fragmentShader;
        fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
        glCompileShader(fragmentShader);

着色器程序

        unsigned int shaderProgram;//创建一个着色器程序对象
        shaderProgram = glCreateProgram();//创建一个程序,并返回新创建程序对象的ID引用
        glAttachShader(shaderProgram, vertexShader);//将着色器附加到程序对象上
        glAttachShader(shaderProgram, fragmentShader);
        glLinkProgram(shaderProgram);//链接两个着色器
        glUseProgram(shaderProgram); //激活这个程序对象,以后每个着色器调用和渲染调用都会使用这个程序对象

        glDeleteShader(vertexShader);//链接以后就不需要了
        glDeleteShader(fragmentShader);

链接顶点属性

unsigned int VAO;
    unsigned int VBO;
    glGenBuffers(1, &VBO);//生成一个VBO缓冲对象
    glGenVertexArrays(1, &VAO);
    // 1. 绑定VAO
    glBindVertexArray(VAO);
    // 2. 把顶点数组复制到缓冲中供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定缓冲对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把之前定义的顶点数据复制到缓冲的内存中
    //glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
    // 它的第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。
    // 第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。
    // 第三个参数是我们希望发送的实际数据。
    // 第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
    //    GL_STATIC_DRAW :数据不会或几乎不会改变。
    //    GL_DYNAMIC_DRAW:数据会被改变很多。
    //    GL_STREAM_DRAW :数据每次绘制时都会改变。
    // 3. 设置顶点属性指针
//链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
    //第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
    //第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec * 都是由浮点数值组成的)。
    //第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是 - 1)到1之间。我们把它设置为GL_FALSE。
    //第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
    //最后一个参数的类型是void * ,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。
    glEnableVertexAttribArray(0);//启用顶点属性,以顶点属性位置值作为参数

绘制三角形

        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        //第一个参数是我们打算绘制的OpenGL图元的类型
        // 第二个参数指定了顶点数组的起始索引
        // 最后一个参数指定我们打算绘制多少个顶点

之后三角形就可以绘制出来了

元素缓冲对象

    unsigned int VAO;
    unsigned int VBO;
    unsigned int EBO;    //EBO 元素缓冲对象

    glGenBuffers(1, &EBO);//生成元素缓冲对象
    glGenBuffers(1, &VBO);//生成一个VBO缓冲对象
    glGenVertexArrays(1, &VAO);//生成顶点数组对象

     ..:: 初始化代码 :: ..
    // 1. 绑定顶点数组对象
    glBindVertexArray(VAO);
    //2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//绑定缓冲对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把之前定义的顶点数据复制到缓冲的内存中
    //glBufferData是一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
    // 它的第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。
    // 第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。
    // 第三个参数是我们希望发送的实际数据。
    // 第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
    //    GL_STATIC_DRAW :数据不会或几乎不会改变。
    //    GL_DYNAMIC_DRAW:数据会被改变很多。
    //    GL_STREAM_DRAW :数据每次绘制时都会改变。
    // 3. 复制我们的索引数组到一个索引缓冲中,供OpenGL使用
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    // 4. 设定顶点属性指针
    //链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //第一个参数指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
    //第二个参数指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
    //第三个参数指定数据的类型,这里是GL_FLOAT(GLSL中vec * 都是由浮点数值组成的)。
    //第四个参数定义我们是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是 - 1)到1之间。我们把它设置为GL_FALSE。
    //第五个参数叫做步长(Stride),它告诉我们在连续的顶点属性组之间的间隔。由于下个组位置数据在3个float之后,我们把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔,我们在后面会看到更多的例子(译注: 这个参数的意思简单说就是从这个属性第二次出现的地方到整个数组0位置之间有多少字节)。
    //最后一个参数的类型是void * ,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。由于位置数据在数组的开头,所以这里是0。我们会在后面详细解释这个参数。
    glEnableVertexAttribArray(0);//启用顶点属性,以顶点属性位置值作为参数

    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//线框模式渲染
    //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//默认模式渲染

渲染循环

        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
       // glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        //第一个参数是我们打算绘制的OpenGL图元的类型
        // 第二个参数指定了顶点数组的起始索引
        // 最后一个参数指定我们打算绘制多少个顶点
        glBindVertexArray(0);//解绑

练习

1.添加更多顶点到数据中,使用glDrawArrays,尝试绘制两个彼此相连的三角形:参考解答

增加顶点数据

//顶点数据
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,   // 左上角

    0.5f, 0.5f, 0.0f,   // 右上角
    -0.5f, -0.5f, 0.0f // 左下角
};

修改 glDrawElements函数的顶点数量为6

        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
        //glDrawArrays(GL_TRIANGLES, 0, 6);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

2.创建相同的两个三角形,但对它们的数据使用不同的VAO和VBO 

创建三角形

//顶点数据
float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
};
//顶点数据
float vertices1[] = {
    -0.5f, 0.5f, 0.0f,   // 左上角
     0.5f, 0.5f, 0.0f,   // 右上角
    -0.5f, -0.5f, 0.0f // 左下角
};

传输顶点数据

    glGenBuffers(1, &EBO);//生成元素缓冲对象
    glGenBuffers(2, VBO);//生成一个VBO缓冲对象
    glGenVertexArrays(2, VAO);//生成顶点数组对象

    // 1. 绑定顶点数组对象
    glBindVertexArray(VAO[0]);
    //2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);//绑定缓冲对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把之前定义的顶点数据复制到缓冲的内存中
    // 3. 设定顶点属性指针
    //链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);//启用顶点属性,

    glBindVertexArray(VAO[1]);
    //2. 把我们的顶点数组复制到一个顶点缓冲中,供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO[1]);//绑定缓冲对象
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices1, GL_STATIC_DRAW);//把之前定义的顶点数据复制到缓冲的内存中
    // 3. 设定顶点属性指针
    //链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);//启用顶点属性,以顶点属性位置值作为参数

循环渲染

        glUseProgram(shaderProgram);
        glBindVertexArray(VAO[0]);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glBindVertexArray(VAO[1]);
        glDrawArrays(GL_TRIANGLES, 0, 3);

3.创建两个着色器程序,第二个程序使用一个不同的片段着色器,输出黄色;再次绘制这两个三角形,让其中一个输出为黄色:参考解答

增加一个片段着色器,我改成了输出红色

const char* fragmentShaderSource1 = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"FragColor = vec4(1.0f, 0.0f, 0.2f, 1.0f);\n"
" }\n";

片段着色器源码附加到着色器对象上

    fragmentShader2 = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader2, 1, &fragmentShaderSource1, NULL);
    glCompileShader(fragmentShader2);

创建一个着色器程序对象并且链接顶点和片段着色器

 shaderProgram2 = glCreateProgram();//创建一个程序,并返回新创建程序对象的ID引用
    glAttachShader(shaderProgram2, vertexShader);//将着色器附加到程序对象上
    glAttachShader(shaderProgram2, fragmentShader2);
    glLinkProgram(shaderProgram2);//链接两个着色器

然后就可以使用这个着色器程序了

        glUseProgram(shaderProgram);
        glBindVertexArray(VAO[0]);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glUseProgram(shaderProgram2);
        glBindVertexArray(VAO[1]);
        glDrawArrays(GL_TRIANGLES, 0, 3);

输出结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/441021.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

编译内核错误 multiple definition of `yylloc‘

编译内核错误 # make ARCHarm CROSS_COMPILEarm-mix410-linux- uImageHOSTLD scripts/dtc/dtc /usr/bin/ld: scripts/dtc/dtc-parser.tab.o:(.bss0x10): multiple definition of yylloc; scripts/dtc/dtc-lexer.lex.o:(.bss0x0): first defined here collect2: error: ld ret…

昏暗场景增强-低照度增强-弱光增强(附代码)

引言 随着现代科技的发展,图像采集设备已经渗透到生活的方方面面,然而在昏暗场景、低照度或弱光条件下,图像的质量往往受到严重影响,表现为亮度不足、对比度低下、色彩失真以及细节丢失等问题。这类图像对于人眼识别和计算机视觉…

FPGA IBUFG

IBUFG和IBUFGDS的输入端仅仅与芯片的专用全局时钟输入管脚有物理连接,与普通IO和其它内部CLB等没有物理连接。 所以,IBUFG输入的不能直接接另外信号。 GTH transceiver primitives are called GTHE3_COMMON and GTHE3_CHANNEL in UltraScale FPGAs, an…

部署LVS+Keepalived高可用群集(抢占模式,非抢占模式,延迟模式)

目录 一、LVSKeepalived高可用群集 1、实验环境 2、 主和备keepalived的配置 2.1 yum安装ipvsadm和keepalived工具 2.2 添加ip_vs模块并开启ipvsadm 2.3 修改keepalived的配置文件 2.4 调整proc响应参数,关闭linux内核的重定向参数响应 2.5 将主服务器的kee…

SpringBoot整合Redis实现分布式锁

SpringBoot整合Redis实现分布式锁 分布式系统为什么要使用分布式锁? 首先,分布式系统是由多个独立节点组成的,这些节点可能运行在不同的物理或虚拟机器上,它们通过网络进行通信和协作。在这样的环境中,多个节点可能同…

群智能优化算法:巨型犰狳优化算法(GAO)求解23个基准函数(提供MATLAB代码)

一、巨型犰狳优化算法 巨型犰狳优化算法(Giant Armadillo Optimization,GAO)由Omar Alsayyed等人于2023年提出,该算法模仿了巨型犰狳在野外的自然行为。GAO设计的基本灵感来自巨型犰狳向猎物位置移动和挖掘白蚁丘的狩猎策略。GAO…

LLM 构建Data Muti-Agents 赋能数据分析平台的实践之①:数据采集

一、 概述 在推进产业数字化的过程中,数据作为最重要的资源是优化产业管控过程和提升产业数字化水平的基础一环,如何实现数据采集工作的便利化、高效化、智能化是降低数据分析体系运转成本以及推动数据价值挖掘体系的基础手段。随着数字化在产业端的推进…

电脑小问题:Windows更新后黑屏

Windows 更新后黑屏解决方法 在 Windows 更新后,伴随了一个小问题,电脑启动后出现了桌面黑屏。原因可能是火绒把 explorer.exe 当病毒处理了。 下面讲解 Windows 更新后黑屏的解决方法,步骤如下: 1. 按 ctrl alt delete 组合键…

JAVA实战开源项目:生活废品回收系统(Vue+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容三、界面展示3.1 登录注册3.2 资源类型&资源品类模块3.3 回收机构模块3.4 资源求购/出售/交易单模块3.5 客服咨询模块 四、免责说明 一、摘要 1.1 项目介绍 生活废品回收系统是可持续发展的解决方案,旨在鼓…

【操作系统概念】 第9章:虚拟内存管理

文章目录 0.前言9.1 背景9.2 按需调页9.2.1 基本概念9.2.2 按需调页的性能 9.3 写时复制9.4 页面置换9.4.1 基本页置换9.4.2 FIFO页置换9.4.3 最优(Optimal)置换9.4.4 LRU(Least Recently Used)页置换9.4.5 近似LRU页置换9.4.6 页缓冲算法 9.5 帧分配9.5…

遥遥领先!基于transformer变体的时间序列预测新SOTA!

目前,以CNN、RNN和 Transformer 模型为代表的深度学习算法已经超越了传统机器学习算法,成为了时间序列预测领域一个新的研究趋向。这其中,基于Transformer架构的模型在时间序列预测中取得了丰硕的成果。 Transformer模型因其强大的序列建模能…

JVM-垃圾收集器G1

G1垃圾回收器 概述: 是一款面向服务器的垃圾收集器,主要针对配备多个处理器及大容量内存的机器. 以极高效率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.G1保留了年轻代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连…

Apache服务的搭建与配置

一、apache安装 systemctl stop firewalldsystemctl disable firewalldsetenforce 0yum -y install httpdsystemctl start httpdnetstat -ntlp | grep 80 二、认识主配置文件 # vim /etc/httpd/conf/httpd.conf ServerRoot "/etc/httpd" #定义工作目…

【linux驱动开发】IO模型之同步IO、异步IO、IO多路复用

文章目录 IO的概述IO模型的实现阻塞IO非阻塞IOIO多路复用信号驱动异步IO 编译与测试说明 IO的概述 io,英文名称为inoput与output,表示输入与输出。 在冯诺依曼结构计算机中,计算机由 运算器、控制器、存储器、输入、输出五部分组成&#xf…

华为云开年采购季Web及移动App上云体验,助力软件行业创新发展

随着云化、智能化浪潮的进一步深入,越来越多的应用软件开发商选择将核心产品从本地IDC机房搬迁到公有云上。但同时,软件开发商们也非常在意公有云厂商的可靠性与安全性,希望能够选择一家更加稳定可靠的云服务商,确保自身业务的连续…

MS2548,自动方向控制、半双工 RS-485 收发器,可替代MAX13487

产品简述 MS2548 是一个 5V 供电、半双工 RS-485 收发器。 芯片具有自动换向控制功能,可用于隔离 485 端口, 驱动器输入与使能信号一起配合控制芯片的状态, 驱动差分总线。 芯片内部集成热插拔保护和过温保护功能。接 收器输入阻抗为 1…

华为OD机试真题-测试用例执行计划

测试用例执行计划 题目描述: 某个产品当前迭代周期内有N个特性({F1,F2,...,FN})需要进行覆盖测试,每个特性都被评估了对应的优先级,特性使用其ID作为下标进行标识。 设计了M个测试用例({T1,T2,...,TM}),每个用例对应了一个覆盖特…

网络工程师笔记9

动态路由 RIP路由协议 配置简单 易于维护 适用于小型网络 周期性是30s发一次

分享关于如何解决系统设计问题的逐步框架

公司广泛采用系统设计面试,因为在这些面试中测试的沟通和解决问题的技能与软件工程师日常工作所需的技能相似。面试官的评估基于她如何分析一个模糊的问题以及如何逐步解决问题。测试的能力还包括她如何解释这个想法,与他人讨论,以及评估和优…

主网NFT的发布合约

1.什么是nft? NFT:Non-fungible-token 非同质化货币 2.新建suimove项目 使用sui move new 项目名命令新建sui move项目 sui move new nft_qyx项目结构如下: 3.写nft合约 module qyx123::nft{use sui::object::{Self, UID};use sui::transfer;use sui::tx_context::{Sel…