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. 绑定VAOglBindVertexArray(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.hqwc.cn/news/522966.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2001-2022年上市公司利润表数据

2001-2022年上市公司利润表数据 1、时间:2001.12.31-2022.12.31 2、范围:上市公司 3、指标:证券代码、证券简称、统计截止日期、报表类型、投资收益、其中:对联营企业和合营企业的投资收益、公允价值变动收益、营业利润、其他综…

严刑拷打_微服务

文章详情 :😊 作者:Lion J 💖 主页: https://blog.csdn.net/weixin_69252724 🎉 主题: 微服务相关知识 ⏱️ 创作时间:2024年03月8日 ———————————————— 文章目…

Redis核心数据结构之字典(二)

字典 解决键冲突 当有两个或以上数量的键被分配到了一个哈希表数组的同一个索引上面,我们称这些键发生了冲突(collision)。 Redis的哈希表使用链地址法(separate chaining)来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以…

c++ 常用的STL

前言 写这篇博客目的是为了记录在刷算法题中使用过的STL,因为有些不太常用的会遗忘。这篇博客只是作为笔记,不是详细的STL,因此只会对常用方法说明,不会详细介绍。此外在后面用到新的STL内容时会再补充。 列队 基础列队 基本列…

社区店选址标准:如何选择适合你业务的理想位置

选址是实体店成功的关键因素之一,而社区店更是要紧密结合社区的特点来选择。 作为一名鲜奶吧开店5年的创业者,我将为大家分享一些实用的社区店选址标准。 1、社区类型: 首先要明确你的目标客户群体,然后选择与之匹配的社区类型…

办公电脑换成MacBookPro半年之后……

小白是从2008年开始接触电脑的,当时朋友给我注册的第一个QQ账号是2008年4月。 从此,小白一直认为电脑全部都是Windows系统。直到上大学那年,看到了外教老师的MacBookPro…… 折腾电脑的开始居然是起源于诺基亚手机,给半智能S40的…

python三剑客之一——Numpy

温故而知新,借着工作需要用到Numpy的机会重新学习一遍Numpy。 Numpy是一个运行速度非常快的数学库,主要用于数组计算,包含如下: 一个强大的N维数组对象ndarray【Nd(Dimension维度)array】 广播功能函数 整…

瑞_Redis_短信登录(一)

文章目录 项目介绍1 短信登录1.1 项目准备1.1.1 导入SQL1.1.2 导入后端项目1.1.3 导入前端项目 🙊 前言:本文章为瑞_系列专栏之《Redis》的实战篇的短信登录章节的项目准备小节。由于博主是从B站黑马程序员的《Redis》学习其相关知识,所以本系…

Androidstudio实现登录按钮按下变色

在activity_main.xml中&#xff0c;写如下代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"androi…

个性化科研服务 | 付费分析试营业正式启动啦!定制你的专属生信分析!可提供1v1答疑!

之前在 干货满满 | 给生信小白的入门小建议 | 掏心掏肺版 中有提到&#xff0c;如果小伙伴们真的想学好生信&#xff0c;那编程能力是必须要有的&#xff01;但是可能有些小伙伴们并没有那么多的时间从头开始学习编程&#xff0c;又或是希望有人指导或者协助完成生信分析工作&a…

微信小程序(五十四)腾讯位置服务示范(2024/3/8更新)

教程如下&#xff1a; 上一篇 1.先在官网注册一下账号&#xff08;该绑定的都绑定一下&#xff09; 腾讯位置服务官网 2.进入控制台 3.创建应用 3. 额度分配 4.下载微信小程序SDK 微信小程序SDK下载渠道 5.解压将俩js文件放在项目合适的地方 6.加入安全域名or设置不验证合…

Kafka MQ 生产者和消费者

Kafka MQ 生产者和消费者 Kafka 的客户端就是 Kafka 系统的用户&#xff0c;它们被分为两种基本类型:生产者和消费者。除 此之外&#xff0c;还有其他高级客户端 API——用于数据集成的 Kafka Connect API 和用于流式处理 的 Kafka Streams。这些高级客户端 API 使用生产者和消…