LearnOpenGL-笔记-其九

今天让我们完结高级OpenGL的部分:

Instancing

很多时候,在场景中包含有大量实例的时候,光是调用GPU的绘制函数这个过程都会带来非常大的开销,因此我们需要想办法在每一次调用GPU的绘制函数时尽可能多地绘制,这个过程就是我们的实例化。

具体来说,我们来看调用glDrawArraysInstanced函数的代码:

float quadVertices[] = {// 位置          // 颜色-0.05f,  0.05f,  1.0f, 0.0f, 0.0f,0.05f, -0.05f,  0.0f, 1.0f, 0.0f,-0.05f, -0.05f,  0.0f, 0.0f, 1.0f,-0.05f,  0.05f,  1.0f, 0.0f, 0.0f,0.05f, -0.05f,  0.0f, 1.0f, 0.0f,   0.05f,  0.05f,  0.0f, 1.0f, 1.0f                   
}; 

这是我们的顶点数组,二维的位置坐标和三维的RGB颜色值。

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;out vec3 fColor;uniform vec2 offsets[100];void main()
{vec2 offset = offsets[gl_InstanceID];gl_Position = vec4(aPos + offset, 0.0, 1.0);fColor = aColor;
}

这个就是我们的顶点着色器,能够看到我们有一个uniform偏移量数组,我们用gl_InstanceID作为数组的索引。就像我们之前所言,gl_InstanceID是一个自增的内建变量,我们使用这个变量可以省去一个for循环的过程,当然,前提是我们调用glDrawArraysInstanced函数。

glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);

我们调用glDrawArraysInstanecd和glDrawArrays的方式没有区别,只是多了一个实例的数目,这里我们是100,这样的话我们的gl_InstanceID就会从0自增到100。

虽然这种直接在着色器里定义好偏差数组的做法非常的方便快捷,但是显然在真正的应用中,我们需要的实例数量还会不断提高。作为定义在着色器里的uniform类型变量是有自己的数据的上限的,这个时候我们就需要去寻找新的方法来解决这个问题,这个新的方法就是实例化数组。

我们用一个代码实例就能知道效果如何:

// generate a list of 100 quad locations/translation-vectors
// ---------------------------------------------------------glm::vec2 translations[100];int index = 0;float offset = 0.1f;for (int y = -10; y < 10; y += 2){for (int x = -10; x < 10; x += 2){glm::vec2 translation;translation.x = (float)x / 10.0f + offset;translation.y = (float)y / 10.0f + offset;translations[index++] = translation;}}

在这里我们定义了一个大小为100的二维向量数组,他们的每一个值都不一样。

    unsigned int instanceVBO;glGenBuffers(1, &instanceVBO);glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);glBindBuffer(GL_ARRAY_BUFFER, 0);

我们专门生成一个VBO对象去把这些值加载进缓冲中。

    float quadVertices[] = {// positions     // colors-0.05f,  0.05f,  1.0f, 0.0f, 0.0f,0.05f, -0.05f,  0.0f, 1.0f, 0.0f,-0.05f, -0.05f,  0.0f, 0.0f, 1.0f,-0.05f,  0.05f,  1.0f, 0.0f, 0.0f,0.05f, -0.05f,  0.0f, 1.0f, 0.0f,0.05f,  0.05f,  0.0f, 1.0f, 1.0f};

这是我们具体的顶点数组,每个顶点有两个属性:位置和颜色,其实一个是二维向量一个是三维向量。

    unsigned int quadVAO, quadVBO;glGenVertexArrays(1, &quadVAO);glGenBuffers(1, &quadVBO);glBindVertexArray(quadVAO);glBindBuffer(GL_ARRAY_BUFFER, quadVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);glEnableVertexAttribArray(1);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));// also set instance dataglEnableVertexAttribArray(2);glBindBuffer(GL_ARRAY_BUFFER, instanceVBO); // this attribute comes from a different vertex bufferglVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);glBindBuffer(GL_ARRAY_BUFFER, 0);glVertexAttribDivisor(2, 1); // tell OpenGL this is an instanced vertex attribute.

这里我们定义并配置我们的VAO对象和VBO对象,注意我们的VBO的配置过程中,我们把location为0和1的数据交给了我们的quadVBO,而到了location为2的第三个对象实例化数据,我们切换VBO对象为instanceVBO,然后我们还调用了glVertexAttribDivisor函数:这个函数用于告诉GPU我们的实例ID(就是那个内建变量gl_InstanceID)每隔一个实例更新一次(2代表location,1则是隔1个实例自增一次)。

搭配上我们的顶点着色器的定义:

#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;out vec3 fColor;void main()
{fColor = aColor;gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
}

效果如图:

现在让我们来上点强度,我们把需要渲染的模型的数量提高N个数量级:我们来生成一个小行星带的效果。

小行星带,显然难的不是行星而是行星带比较难处理,当我们想象行星带时,我们很容易想到大量的碎石,显然这些碎石的数量远远大小我们的Uniform的数据大小上限(非常大的数量级)。

    unsigned int amount = 100000;glm::mat4* modelMatrices;modelMatrices = new glm::mat4[amount];srand(static_cast<unsigned int>(glfwGetTime())); // initialize random seedfloat radius = 150.0;float offset = 25.0f;for (unsigned int i = 0; i < amount; i++){glm::mat4 model = glm::mat4(1.0f);// 1. translation: displace along circle with 'radius' in range [-offset, offset]float angle = (float)i / (float)amount * 360.0f;float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;float x = sin(angle) * radius + displacement;displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;float y = displacement * 0.4f; // keep height of asteroid field smaller compared to width of x and zdisplacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;float z = cos(angle) * radius + displacement;model = glm::translate(model, glm::vec3(x, y, z));// 2. scale: Scale between 0.05 and 0.25ffloat scale = static_cast<float>((rand() % 20) / 100.0 + 0.05);model = glm::scale(model, glm::vec3(scale));// 3. rotation: add random rotation around a (semi)randomly picked rotation axis vectorfloat rotAngle = static_cast<float>((rand() % 360));model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));// 4. now add to list of matricesmodelMatrices[i] = model;}

我们生成10万个模型变换矩阵,然后将其坐标设置为绕着圆心的圆上。

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in mat4 aInstanceMatrix;out vec2 TexCoords;uniform mat4 projection;
uniform mat4 view;void main()
{TexCoords = aTexCoords;gl_Position = projection * view * aInstanceMatrix * vec4(aPos, 1.0f); 
}

这里我们把模型变化矩阵作为一个变量传进顶点着色器,注意我们传的是一个mat4变量,而顶点着色器的大小上限是一个vec4变量(mat4是4个vec4),所以我们需要进行一些额外的处理。

    // configure instanced array// -------------------------unsigned int buffer;glGenBuffers(1, &buffer);glBindBuffer(GL_ARRAY_BUFFER, buffer);glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);// set transformation matrices as an instance vertex attribute (with divisor 1)// note: we're cheating a little by taking the, now publicly declared, VAO of the model's mesh(es) and adding new vertexAttribPointers// normally you'd want to do this in a more organized fashion, but for learning purposes this will do.// -----------------------------------------------------------------------------------------------------------------------------------for (unsigned int i = 0; i < rock.meshes.size(); i++){unsigned int VAO = rock.meshes[i].VAO;glBindVertexArray(VAO);// set attribute pointers for matrix (4 times vec4)glEnableVertexAttribArray(3);glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);glEnableVertexAttribArray(4);glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));glEnableVertexAttribArray(5);glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(2 * sizeof(glm::vec4)));glEnableVertexAttribArray(6);glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(3 * sizeof(glm::vec4)));glVertexAttribDivisor(3, 1);glVertexAttribDivisor(4, 1);glVertexAttribDivisor(5, 1);glVertexAttribDivisor(6, 1);glBindVertexArray(0);}

我们可以看到,我们生成VBO之后,我们从指向模型变换矩阵的数组指针开始,我们需要单独取出每个子碎石的VAO来绑定庞大的模型变换矩阵数组,我们从layout=3的位置开始一行一行地读取,然后每一个实例都要更新一个实例ID,因为一个顶点数据的最大大小为vec4,所以我们需要四个顶点数据组成完整的mat4变量。这样做的意思其实就是我们将多达十万个mat4的数据量直接一次性全部传入缓存中,这样的话我们只用单独Draw Call一次就可以绘制所有的碎石。

效果如图:

Anti Aliasing

抗锯齿已经是一个经久不衰的老问题了:

很喜欢闫老师的一句话:世界上所有的失真现象都可以归结为采样率不足。

SSAA事实上某种程度来说也算是一种针对抗锯齿的通解,用更高的算力开销来从根本上解决问题,但是我们如果想要真正体现出思想优越之处的做法,那就得从头开始做。

为了理解什么是多重采样(Multisampling),以及它是如何解决锯齿问题的,我们有必要更加深入地了解OpenGL光栅器的工作方式。

光栅器是位于最终处理过的顶点之后到片段着色器之前所经过的所有的算法与过程的总和。光栅器会将一个图元的所有顶点作为输入,并将它转换为一系列的片段。顶点坐标理论上可以取任意值,但片段不行,因为它们受限于你窗口的分辨率。顶点坐标与片段之间几乎永远也不会有一对一的映射,所以光栅器必须以某种方式来决定每个顶点最终所在的片段/屏幕坐标。

是的,多重采样的本质就是提高每个像素的采样点数量,这样我们可以更清晰地了解具体某个像素有多少个采样点被采样,从而体现出颜色的层次并大大减少锯齿数量。

明白了我们的多重采样的基本原理之后,让我们回到OpenGL之中的抗锯齿技术。

我们的OpenGL之中已经帮我们定义好了MSAA方法,所以我们只需要在生成窗口系统时去显示地写明我们的采样缓冲数量即可,需要注意的是,这里的缓冲是包含所有的帧缓冲,也就是除了颜色缓冲以外还有深度缓冲和模板缓冲。

由于后续的OpenGL课程中并没有展开对抗锯齿算法的详细介绍与具体优化,我也就不再浪费篇幅。

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

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

相关文章

PDF预览-搜索并高亮文本

在PDF.js中实现搜索高亮功能可以通过自定义一些代码来实现。PDF.js 是一个通用的、基于Web的PDF阅读器&#xff0c;它允许你在网页上嵌入PDF文件&#xff0c;并提供基本的阅读功能。要实现搜索并高亮显示文本&#xff0c;你可以通过以下几个步骤来完成&#xff1a; 1. 引入PDF…

二叉树——队列bfs专题

1.N叉树的层序遍历 我们之前遇到过二叉树的层序遍历&#xff0c;只需要用队列先进先出的特性就可以达到层序遍历的目的。 而这里不是二叉树&#xff0c;也就是说让节点的孩子入队列时不仅仅是左右孩子了&#xff0c;而是它的所有孩子。而我们看这棵多叉树的构造&#xff0c;它…

Python高级爬虫之JS逆向+安卓逆向1.1节-搭建Python开发环境

目录 引言&#xff1a; 1.1.1 为什么要安装Python? 1.1.2 下载Python解释器 1.1.3 安装Python解释器 1.1.4 测试是否安装成功 1.1.5 跟大神学高级爬虫安卓逆向 引言&#xff1a; 大神薯条老师的高级爬虫安卓逆向教程&#xff1a; 这套爬虫教程会系统讲解爬虫的初级&…

Windows 安装和使用 ElasticSearch

SpringBoot3 整合 Elasticsearch 1. ElasticSearch 1.1 ES &#xff08;1&#xff09;ES 是一个开源的分布式搜索和分析引擎&#xff0c;专为处理大模型数据而设计&#xff0c;它能够实现近乎实时的数据检索、分析和可视化&#xff0c;广泛用于全文搜索、日志分析和监控&…

matplotlib初探

库引入 import matplotlib.pyplot as pltpyplot.figure 创建新图形或激活现有图形

NVM 多版本Node.js 管理全指南(Windows系统)

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、全栈领域优质创作者、高级开发工程师、高级信息系统项目管理师、系统架构师&#xff0c;数学与应用数学专业&#xff0c;10年以上多种混合语言开发经验&#xff0c;从事DICOM医学影像开发领域多年&#xff0c;熟悉DICOM协议及…

实验室预约|实验室预约小程序|基于Java+vue微信小程序的实验室预约管理系统设计与实现(源码+数据库+文档)

实验室预约小程序 目录 基于微信小程序的实验室预约管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、微信小程序前台 2、管理员后台 &#xff08;1&#xff09;管理员登录 &#xff08;2&#xff09;实验室管理 &#xff08;3&#xff09;公告信息管理…

SpringBoot底层-数据源自动配置类

SpringBoot默认使用Hikari连接池&#xff0c;当我们想要切换成Druid连接池&#xff0c;底层原理是怎样呢 SpringBoot默认连接池——Hikari 在spring-boot-autoconfiguration包内有一个DataSourceConfiguraion配置类 abstract class DataSourceConfiguration {Configuration(p…

面试算法高频03-递归

认识递归 递归的概念与特性&#xff1a;递归本质类似循环&#xff0c;是通过函数体进行的循环操作。借助电影《盗梦空间》类比&#xff0c;递归如同主角在不同梦境层穿梭&#xff0c;向下进入不同递归层&#xff0c;向上能回到原来一层&#xff0c;每一层环境和周围元素相似&a…

linux Gitkraken 破解

ubuntu 安装 Gitkraken 9.x Pro 版本_gitcracken.git-CSDN博客

设计模式简述(十一)装饰器模式

装饰器模式 描述基本使用使用 描述 装饰器模式是一种功能型模式 用于动态增强对象的功能 这么一说感觉上和代理模式有些类似 抽象装饰器 要实现原有业务接口&#xff0c;并注入原有业务对象 至于对原有业务对象的调用&#xff0c;可以采用private业务对象 实现业务接口方法的…

【NetCore】ControllerBase:ASP.NET Core 中的基石类

ControllerBase:ASP.NET Core 中的基石类 一、什么是 ControllerBase?二、ControllerBase 的主要功能三、ControllerBase 的常用属性四、ControllerBase 的常用方法2. 模型绑定与验证3. 依赖注入五、ControllerBase 与 Controller 的区别六、实际开发中的最佳实践七、总结在 …

DE2-115分秒计数器

一、模块设计 如若不清楚怎么模块化&#xff0c;请看https://blog.csdn.net/szyugly/article/details/146379170?spm1001.2014.3001.5501 1.1顶层模块 module top_counter(input wire CLOCK_50, // 50MHz时钟input wire KEY0, // 暂停/继续按键out…

ubuntu git cola gui

直接的方法&#xff0c; samba&#xff0c; win 里用 tortoiseSVN 需要先在命令行&#xff0c;运行 git 命令&#xff0c;看到操作提示&#xff0c; 按照提示做 然后右键看 git diff 其它的方法 linux下可视化git工具git-cola安装与使用&#xff08;HTTP方式&#xff09;_git…

每日一题(小白)回溯篇4

深度优先搜索题&#xff1a;找到最长的路径&#xff0c;计算这样的路径有多少条&#xff08;使用回溯&#xff09; 分析题意可以得知&#xff0c;每次向前后左右走一步&#xff0c;直至走完16步就算一条走通路径。要求条件是不能超出4*4的范围&#xff0c;不能重复之前的路径。…

【数据分享】2000—2020年我国250m精度灌溉农田栅格数据(免费获取)

灌溉农田是指通过水利灌溉为农作物提供必要水分&#xff0c;以维持其生长需求的农田类型。灌溉农田占全球农田的20%&#xff0c;占全球粮食产量的40%。但其消耗了60%-70%的淡水和80%-90%的消耗性用水量。中国是世界上灌溉面积最大的农业大国&#xff0c;但中国仅占世界上8%的农…

MySQL-SQL-DML语句、INSER添加数据、UPDATE更新数据、DELETE删除数据

一. DML 1. DML的英文全称是Data Manipulation Language(数据操作语言)&#xff0c;用来对数据库中表的数据记录进行增、删、改操作。 2. 添加数据(INSERT)&#xff1b;修改数据(UPDATE)&#xff1b;删除数据(DELETE) 二. DML-INSER添加数据 -- DML insert -- 指定字段添加数…

使用SymPy求解矩阵微分方程

引言 在数学、物理、工程等领域,微分方程常常被用来描述系统的变化和动态过程。对于多变量系统或者多方程系统,矩阵微分方程是非常常见的,它可以用来描述如电路、控制系统、振动系统等复杂的动态行为。今天,我们将通过Python 中的 SymPy 库来求解矩阵微分方程,帮助大家轻…

Sentinel实战(五)、系统保护规则、限流后统一处理及sentinel持久化配置

Spring Cloud Alibaba-Sentinel实战(五)、系统保护规则、限流后统一处理及sentinel持久化配置 一、系统保护规则一)、系统规则支持的模式二)、新增系统规则界面三)、demo测试二、限流后统一处理实操demo三、sentinel持久化配一、系统保护规则 系统保护规则是从应用级别的…

【百日精通JAVA | SQL篇 | 第四篇】约束

SQL这一块没什么难度&#xff0c;主要是一个熟练度&#xff0c;稍微上点难度的地方&#xff0c;其实在于查&#xff0c;比较复杂&#xff0c;涉及到很多问题。 指定列插入 使用指定列插入的时候&#xff0c;未被指定的列使用默认值进行存储&#xff0c;默认值为空。 默认值设置…