【OpenGL】LearnOpenGL学习笔记20 - 实例化 Instancing

上接:https://blog.csdn.net/weixin_44506615/article/details/151156446?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl | https://github.com/Duo1J/LearnOpenGL

实例化 Instancing

以往当我们在场景中要大量绘制相同模型的物体时,代码会像是这样

for (unsigned int i = 0; i < modelCount; i++)
{Bind();glDrawArrays(GL_TRIANGLES, 0, verticesCount);
}

这样CPU会发起很多次绘制调用 (DrawCall) ,这个操作是相对比较缓慢的,很快就会称为性能的瓶颈
所以我们将使用实例化的方式,仅需一次绘制调用就可以绘制多个物体

1、gl_InstanceID

要使用实例化渲染,我们只需要将 glDrawArraysglDrawElements
换成 glDrawArraysInstancedglDrawElementsInstanced 即可,这两个新接口在最后多了一个参数为实例数量
在顶点着色器中,我们可以通过 gl_InstanceID 来知道当前绘制的是第几个实例

首先我们先修改一下ModelMesh
Model

// Model.h
/**
* 绘制
*/
void Draw(Shader shader, int instanceCnt = 0);// Model.cpp
void Model::Draw(Shader shader, int instanceCnt)
{for (unsigned int i = 0; i < meshes.size(); ++i){meshes[i].Draw(shader, instanceCnt);}
}

Mesh

// Mesh.h
/**
* 绘制
*/
void Draw(const Shader& shader, int instanceCnt = 0);// Mesh.cpp
void Mesh::Draw(const Shader& shader, int instanceCnt)
{// ...//绘制if (instanceCnt > 1)glDrawElementsInstanced(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0, instanceCnt);elseglDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);// ...
}

然后我们需要初始化实例的偏移数组,接着传入到uniform变量中使用gl_InstanceID读取
Main.cpp

// gl_InstanceID + uniform 实例化偏移数组
glm::vec3 instanceOffsets[9];
int instanceOffsetsIdx = 0;
for (int i = 0; i < 3; i++)
{for (int j = 0; j < 3; j++){glm::vec3 offset;offset.x = i * 5;offset.y = j * 5;offset.z = 0;instanceOffsets[instanceOffsetsIdx++] = offset;}
}// 主循环
// gl_InstanceID + uniform 实例化
for (int i = 0; i < 9; i++)
{shader.SetVec3("offsets[" + std::to_string(i) + "]", instanceOffsets[i]);
}
model.Draw(shader, 9);

最后修改背包的顶点着色器
VertexShader.glsl

#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
// 去掉了多余的Positionuniform mat4 model;
layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};out VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;
} vs_out;// 实例偏移
uniform vec3 offsets[9];void main()
{vec3 offset = offsets[gl_InstanceID];gl_Position = projection * view * model * vec4(aPos + offset, 1.0);FragPos = vec3(model * vec4(aPos + offset, 1.0));vs_out.FragPos = FragPos;Normal = mat3(transpose(inverse(model))) * aNormal;vs_out.Normal = Normal;TexCoords = aTexCoords;vs_out.TexCoords = aTexCoords;
}

编译运行,顺利的话可以看见以下图像
我们只进行了一次绘制调用便绘制了9个背包
实例化Instancing

2、 实例化数组 (Instanced Array)

当我们需要绘制的实例数量较多的时候,我们很可能会超过最大能够发送至着色器的uniform数据大小上限,我们就需要使用实例化数组的方式

实例化数组的使用方式和其他顶点属性类似,我们先修改背包的顶点着色器
VertexShader.glsl

#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 offset; // 实例化数组方式out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
// 去掉了多余的Positionuniform mat4 model;
layout (std140) uniform Matrices
{mat4 view;mat4 projection;
};out VS_OUT {vec3 FragPos;vec3 Normal;vec2 TexCoords;vec3 Debug;
} vs_out;void main()
{gl_Position = projection * view * model * vec4(aPos + offset, 1.0);FragPos = vec3(model * vec4(aPos + offset, 1.0));vs_out.FragPos = FragPos;Normal = mat3(transpose(inverse(model))) * aNormal;vs_out.Normal = Normal;TexCoords = aTexCoords;vs_out.TexCoords = aTexCoords;
}

接下来我们为实例化数组创建VBO并绑定
Main.cpp

// 初始化instanceOffsets ...// 实例化数组方式
for (auto it = model.meshes.begin(); it != model.meshes.end(); ++it)
{// 需要绑定到背包模型的VAO上it->BindVAO();unsigned int instanceVBO;glGenBuffers(1, &instanceVBO);glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * 9, &instanceOffsets[0], GL_STATIC_DRAW);glEnableVertexAttribArray(3);glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glBindBuffer(GL_ARRAY_BUFFER, 0);glVertexAttribDivisor(3, 1);it->UnBindVAO();
}

这里最后我们调用了 glVertexAttribDivisor
这个函数告诉了OpenGL什么时候更新顶点属性的内容至新一组的数据
它第一个参数为需要的顶点属性
第二个参数为 属性除数 (Attribute Divisor)
默认时属性除数为0,告诉OpenGL我们需要在顶点着色器每次迭代时更新
这里我们设置为1,告诉OpenGL我们需要在每渲染一个新实例的时候更新,这里我们每个新实例都需要更新offset
同理,设置为2时表示每两个实例更新一次

上述过程可以考虑封装进Model类,这里先从简直接暴露绑定和解绑VAO的接口
Mesh

// Mesh.h
/**
* 绑定VAO
*/
void BindVAO();/**
* 解绑VAO
*/
void UnBindVAO();// Mesh.cpp
void Mesh::BindVAO()
{glBindVertexArray(VAO);
}void Mesh::UnBindVAO()
{glBindVertexArray(0);
}

最后调用绘制命令
Main.cpp

// 实例化数组方式
model.Draw(shader, 9);

编译运行,顺利的话可以看见和 gl_InstanceID + Uniform 一样的效果
实例化数组

完整代码可在顶部git仓库中找到

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

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

相关文章

MySQL主从不一致?DBA急救手册:14种高频坑点+3分钟定位+无损修复!

MySQL「主从不一致」最常见的成因、快速定位思路以及可落地的修复手段 一、为什么会不一致&#xff1f;14 类高频场景类别典型表现/触发条件快速自检命令/日志1. 从库被写入业务或 DBA 直连从库 UPDATE/INSERTSHOW VARIABLES LIKE read_only 应为 ON2. 复制过滤规则主从 binlog…

AI 网站源码:探秘 SUNO,革新音乐创作的 AI 先锋

在当今数字化浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;正深刻地重塑各个领域&#xff0c;音乐创作也不例外。SUNO 作为一款引领潮流的音乐生成工具&#xff0c;宛如一颗璀璨新星&#xff0c;在音乐创作的天空中熠熠生辉&#xff0c;为音乐爱好者和创作者们带来了前…

Linux:malloc背后的实现细节

目录前言一、先搞懂基础&#xff1a;程序的内存布局&#xff08;关键前提&#xff01;&#xff09;二、malloc的核心实现步骤&#xff08;4层架构拆解&#xff09;第1层&#xff1a;用户调用 → 标准库处理&#xff08;glibc的malloc.c&#xff09;第2层&#xff1a;堆内存池管…

什么是X11转发?

X11 转发&#xff08;X11 forwarding&#xff0c;ssh -X&#xff09;是一种 SSH 协议功能&#xff0c;它允许用户在远程服务器上运行图形化应用程序&#xff0c;并通过本地的显示设备和输入输出设备与这些程序进行交互。它被开发者广泛使用&#xff0c;用于在大规模、异构的服务…

Android Kotlin 动态注册 Broadcast 的完整封装方案

在 Kotlin 中封装动态注册的 Broadcast 可以让你更优雅地管理广播的注册和注销&#xff0c;避免内存泄漏。下面是一个完整的封装方案&#xff1a; 基础封装类 import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import …

VGG改进(8):融合Self-Attention的CNN架构

1. 自注意力机制简介自注意力机制是Transformer架构的核心组件&#xff0c;它能够计算输入序列中每个元素与其他所有元素的相关性。与CNN的局部感受野不同&#xff0c;自注意力机制允许模型直接建立远距离依赖关系&#xff0c;从而捕获全局上下文信息。在计算机视觉中&#xff…

ES6 面试题及详细答案 80题 (33-40)-- Symbol与集合数据结构

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

PG-210-HI 山洪预警系统呼叫端:筑牢山区应急预警 “安全防线”

在山洪灾害多发的山区&#xff0c;及时、准确的预警信息传递是保障群众生命财产安全的关键。由 PG-210-HI 型号构成的山洪预警系统呼叫端主机&#xff0c;凭借其全面的功能、先进的特性与可靠的性能&#xff0c;成为连接管理员与群众的重要应急枢纽&#xff0c;为山区构建起一道…

研学旅游产品设计实训室:赋能产品落地,培养实用人才

1. 研学旅游产品设计实训室的定位与功能 研学旅游产品设计实训室是专门为学生提供研学课程与产品开发、模拟设计、项目推演、成果展示等实践活动的教学空间。该实训室应支持以下功能&#xff1a; 研学主题设计与目标制定&#xff1b; 课程内容与学习方法的选择与整合&#xf…

4215kg轻型载货汽车变速器设计cad+设计说明书

第一章 前言 3 1.1 变速器的发展环绕现状 3 1.2 本次设计目的和意义 4 第二章 传动机构布置方案分析及设计 5 2.1 传动机构结构分析与类型选择 5 2.2变速器主传动方案的选择 5 2.3 倒档传动方案 6 2..4 变速器零、部件结构方案设计 6 2.4.1 齿轮形式 …

9月10日

TCP客户端代码#include<myhead.h> #define SER_IP "192.168.108.179" //服务器&#xff49;&#xff50;地址 #define SER_PORT 8888 //服务器端口号 #define CLI_IP "192.168.108.239" //客户端&#xff49;&#xff50;地址 …

案例开发 - 日程管理 - 第七期

项目改造&#xff0c;进入 demo-schedule 项目中&#xff0c;下载 pinia 依赖在 main.js 中开启 piniaimport { createApp } from vue import App from ./App.vue import router from ./router/router.js import {createPinia} from pinialet pinia createPinia() const app …

infinityfree 网页连接内网穿透 localtunnel会换 还是用frp成功了

模型库首页 魔搭社区 fatedier/frp: A fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet. 我尝试用本机ipv6&#xff0c;失败了 配置文件 - ChmlFrp 香港2才能用 只支持https CNAME解析 | 怊猫科技 | 文档 How to create …

批量更新数据:Mybatis update foreach 和 update case when 写法及比较

在平常的开发工作中&#xff0c;我们经常需要批量更新数据&#xff0c;业务需要每次批量更新几千条数据&#xff0c;采用 update foreach 写法的时候&#xff0c;接口响应 10s 左右&#xff0c;优化后&#xff0c;采用 update ... case when 写法&#xff0c;接口响应 2s 左右。…

Java基础篇04:数组、二维数组

1 数组 数组是一个数据容器&#xff0c;可用来存储一批同类型的数据。 1.1 数组的定义方式 静态初始化 数据类型[][] 数组名 {元素1&#xff0c;元素2&#xff0c;元素3}; string[][] name {"wfs","jsc","qf"} 动态初始化 数据类型[][] 数组名…

unity开发类似个人网站空间

可以用 Unity 开发 “个人网站空间” 类工具&#xff0c;但需要结合其技术特性和适用场景来判断是否合适。以下从技术可行性、优势、局限性、适用场景四个方面具体分析&#xff1a;一、技术可行性Unity 本质是游戏引擎&#xff0c;但具备开发 “桌面应用” 和 “交互内容” 的能…

SDK游戏盾如何实现动态加密

SDK游戏盾的动态加密体系通过​​密钥动态管理、多层加密架构、协议混淆、AI自适应调整及设备绑定​​等多重机制协同作用&#xff0c;实现对游戏数据全生命周期的动态保护&#xff0c;有效抵御中间人攻击、协议破解、重放攻击等威胁。以下从核心技术与实现逻辑展开详细说明&am…

TensorFlow平台介绍

什么是 TensorFlow&#xff1f; TensorFlow 是一个由 Google Brain 团队 开发并维护的 开源、端到端机器学习平台。它的核心是一个强大的数值计算库&#xff0c;特别擅长于使用数据流图来表达复杂的计算任务&#xff0c;尤其适合大规模机器学习和深度学习模型的构建、训练和部署…

TENGJUN防水TYPE-C连接器:立贴结构与IPX7防护的精密融合

在户外电子、智能家居、车载设备等对连接可靠性与空间适配性要求严苛的场景中&#xff0c;连接器不仅是信号与电力传输的“桥梁”&#xff0c;更需抵御潮湿、粉尘等复杂环境的侵蚀。TENGJUN防水TYPE-C连接器以“双排立贴”为核心设计&#xff0c;融合锌合金底座、精准尺寸控制与…

Spring Boot + Vue 项目中使用 Redis 分布式锁案例

加锁使用命令&#xff1a;set lock_key unique_value NX PX 1000NX:等同于SETNX &#xff0c;只有键不存在时才能设置成功PX&#xff1a;设置键的过期时间为10秒unique_value&#xff1a;一个必须是唯一的随机值&#xff08;UUID&#xff09;&#xff0c;通常由客户端生成…