Learn OpenGL 02 你好,三角形

news2024/12/23 18:15:23

图形渲染管线

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

首先,我们以数组的形式传递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);

输出结果

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最新2024年阿里云服务器地域和可用区全球分布表,不只是中国

2024年最新阿里云服务器地域分布表,地域指数据中心所在的地理区域,通常按照数据中心所在的城市划分,例如华北2(北京)地域表示数据中心所在的城市是北京。阿里云地域分为四部分即中国、亚太其他国家、欧洲与美洲和中东&…

superset连接Apache Spark SQL(hive)过程中的各种报错解决

superset连接数据库官方文档:Installing Database Drivers | Superset 我们用的是Apache Spark SQL,所以首先需要安装下pyhive #命令既下载了pyhive也下载了它所依赖的其他安装包 pip install pyhive#多个命令也可下载 pip install sasl pip install th…

设计模式-结构型模式-代理模式

代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。[DP] // 定义接口 interface Subject {void request(); }// 真实主题对象 class RealSubject implements Subject {Overridepublic void request() {System.out.println(&quo…

App自动化测试笔记(十一):综合案例

短信案例 需求 在《短信》应用中,进入发送短信页面,在姓名和内容栏中,输入对应的数据,并点击发送。 包名界面名:com.android.mms/.ui.ConversationList 发送短信页面标识:resource-id,com.and…

乐得瑞 1C to 2C快充线:引领充电数据线新潮流,高效快充解决接口难题

随着科技的不断进步,数据线的接口种类也日渐繁多,但在早些时候,三合一和二合一的数据线因其独特的设计而备受欢迎。这类数据线通常采用USB-A口作为输入端,并集成了Micro USB、Lightning以及USB-C三种接口,满足了当时市…

【二】【算法分析与设计】编程练习

数字三角形 链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 时间限制:C/C 1秒,其他语言2秒 空间限制:C/C 32768K,其他语言65536K 64bit IO Format: %lld 题目描述 KiKi学习了循环,BoBo…

2024年AI辅助研发趋势深度分析

随着人工智能(AI)技术的飞速发展,其在各个行业领域中的应用也日益广泛。特别是在研发领域,AI已经成为变革的先锋,极大地推动了科技进步的步伐。本文将从技术进展、行业应用案例、面临的挑战与机遇、未来趋势预测、法规…

JavaScript实现小球移动(二)

这次采用了封装函数的方法&#xff0c;将小球向左向右移动封装在同一个函数内。 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wi…

接雨水(leetcode hot100)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] …

‘ jupyter ‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。

安装anaconda后&#xff0c;在 Dos黑窗口 运行 jupyter notebook 的两个问题 原因&#xff1a;没配置环境变量 解决方法&#xff1a; 在 系统环境变量Path 中 添加两个地址 这里以anaconda安装在 D:\anaconda\install 下为例 &#xff08;根据个人安装具体位置而定&#xff…

Centos7.9安装glibc2.18后回滚到glibc2.17

对glibc的操作非常危险&#xff0c;如果您对Linux操作系统的操作仅限于查看别人的资料来解决问题的话&#xff0c;我还是比较真诚的劝退你了。只所以还是写下这篇博文&#xff0c;一是为了记录自己排错的过程&#xff0c;二是更正目前网络中一些不太正确的博文&#xff0c;防止…

史上最全AP/mAP通用代码实现(yolov5 txt版本)-下

提示&#xff1a;通用map指标框架代码介绍&#xff0c;直接使用yolov5数据格式&#xff0c;实现论文map指标计算代码解读 文章目录 前言该版本是直接使用yolo数据格式实现map计算&#xff0c;集成txt转json格式内容。 一、map模块整体认识二、map计算应用代码解读三、通用map计…

指针进阶(4)看一下这些与指针有关的题你都会做吗?

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

JVM-垃圾收集器G1

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

供应链管理系统(SCM):得供应链得天下不是空话。

2023-08-26 15:51贝格前端工场 Hi&#xff0c;我是贝格前端工场&#xff0c;优化升级各类管理系统的界面和体验&#xff0c;是我们核心业务之一&#xff0c;欢迎老铁们评论点赞互动&#xff0c;有需求可以私信我们 一、供应链对于企业的重要性 供应链对企业经营的重要性不可…

在外包公司搞了2年,出来技术都没了...

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了2年的的功能…

O2O:Sample Efficient Offline-to-Online Reinforcement Learning

IEEE TKDE 2024 paper Introduction O2O存在策略探索受限以及分布偏移问题&#xff0c;进而导致在线微调阶段样本效率低。文章提出OEMA算法首先使用离线数据训练乐观的探索策略&#xff0c;然后提出基于元学习的优化方法&#xff0c;减少分布偏移并提高O2O的适应过程。 Meth…

Java零基础 - 数组的定义和声明

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

React-Redux中actions

一、同步actions 1.概念 说明&#xff1a;在reducers的同步修改方法中添加action对象参数&#xff0c;在调用actionCreater的时候传递参数&#xff0c;数会被传递到action对象payload属性上。 2.reducers对象 说明&#xff1a;声明函数同时接受参数 const counterStorecre…

DDoS和CC攻击的原理

目前最常见的网络攻击方式就是CC攻击和DDoS攻击这两种&#xff0c;很多互联网企业服务器遭到攻击后接入我们德迅云安全高防时会问到&#xff0c;什么是CC攻击&#xff0c;什么又是DDoS攻击&#xff0c;这两个有什么区别的&#xff0c;其实清楚它们的攻击原理&#xff0c;也就知…