[OpenGL]使用TransformFeedback实现粒子效果
一、简介
本文介绍了如何使用 OpenGL 中的 Transform Feedback 实现粒子效果,最终可以实现下图的效果:
本文的粒子系统实现参考了modern-opengl-tutorial, ogldev-tutorial28 和 粒子系统–喷泉 [OpenGL-Transformfeedback]。
二、使用 TransformFeedback 实现效果
1. Transform Feedback 简介
Transform Feedback 是 OpenGL 中用于获取 vertex shader 和 geometry shader 处理后的顶点数据的一种机制,可以在 GPU 上将 vertex shader, geometry shader 处理后的数据存储到以一个 buffer 中,而不进行接下来的 clipper, Rasterizer 和 Fragment Shader 阶段。
Transform Feedback Buffer 在渲染管线中所处的位置如下图所示:
基于 Transform feedback,我们可以在 GPU 上对多个顶点数据行进并行运算,粒子系统 就是 Transform feedback 的一个典型应用。
2. 粒子系统实现
在实现粒子系统时,使用 update Shader 和 render Shader 两个 着色器:
- update shader 用来更新粒子的状态,包括更新粒子状态、生成新粒子、消灭旧粒子。
- render shader 用来将粒子显示在屏幕上。
粒子系统的实现流程如下:
上图展示了使用 Update shader 和 Render shader 实现粒子系统的流程。图中左侧黄色虚线内为使用 Update shader 更新粒子,右侧蓝色虚线内为使用 Render shader 将粒子渲染到屏幕上,然后再进入下一帧的Update-Render
流程。
在 Update shader 中,输入为 Update input VBO
,输出为 Update output VBO
。在 Render shader 中,Update output VBO
又作为渲染时的输入,Render input VBO
。由于 Transform Feedback 中的在读 一个 VBO
时,不能同时写该 VBO
,及Update input VBO
与 Update output VBO
不能是同一个 buffer object。因此在代码实现使用两个 VBO 交替作为 一个Update-Render
流程中的Update input VBO
和 Update output VBO
。
例如,渲染一个n帧的结果,其 Update input VBO
和 Update output VBO
所代表的 buffer 变换如下所示:
3. 部分代码讲解
3.1. Particle 类
struct Particle
{float Type; // 0: launch, 1: shell, 2 : second shellglm::vec3 Pos;glm::vec3 Velocity;float Life;
};
系统中粒子的类型分为三类, launch, shell 和 second shell。
- launch 类粒子相当于一个发射器,其位置、速度一直保持不变,在 Life 到达一定的数值时生成 shell 类粒子;
- shell 类粒子由 launch 类粒子生成后,获得一个初始的速度,假设只受到重力,根据牛顿第二定律更新自己的 速度、位置。并且 shell 粒子的 Life 在到达一定数值时生成 second shell 类粒子;
- second shell 类粒子初始时于生成该粒子的父粒子具有相同的位置,但是速度不同。 second shell 粒子的 Life 到达一定数值后死亡。
3.2. PaticleSystem 类
a. PaticleSystem
类的变量
class ParticleSystem
{
public:
...
private:bool m_isFirst; // 标记 是否时第一次调用 Render()GLuint m_VAO[2]; // 两个 VAO 分别用于 update 和 render 的输入unsigned int m_update_input_id; // update input id,unsigned int m_render_input_id; // render input id, update output idGLuint m_VBO_TFB[2]; // 两个顶点缓冲区 , 交替作为 update / render bufferGLuint m_TFO[2]; // 两个 transform feedback 对象 TFOShader m_updateShader; // particle update shaderShader m_renderShader; // particle render shaderTexture m_randomTexture; // 随机数纹理Texture m_particleTexture; // 粒子的纹理float m_time; // 系统运行的总时间...
}
b. InitParticleSystem()
初始化 ParticleSystem
class ParticleSystem
{public:...bool InitParticleSystem(const glm::vec3 &Pos){// 1. 生成 初始粒子Particle Particles[MAX_PARTICLES];Particles[0].Type = 0;Particles[0].Pos = Pos;Particles[0].Velocity = glm::vec3(0.0f, 0.01f, 0.0f);Particles[0].Life = 0.0f;// 2. 初始化 VAO, TFO, VBOglGenVertexArrays(2, m_VAO); // 生成 两个 VAOglGenTransformFeedbacks(2, m_TFO); // 生成 两个 TFOglGenBuffers(2, m_VBO_TFB); // 生成 两个 buffer (TFB), 分别绑定到 对应的 VAO 和 TFO 上for (unsigned int i = 0; i < 2; i++){// VAO[i] <- VBO[i]// TFO[i] <- VBO[i]glBindVertexArray(m_VAO[i]);glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_TFO[i]);glBindBuffer(GL_ARRAY_BUFFER, m_VBO_TFB[i]);glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, m_VBO_TFB[i]);glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(Particles), Particles, GL_DYNAMIC_DRAW);glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_VBO_TFB[i]);}// 3. 初始化 update shader, render shader// update shaderconst char *feedbackVaryings[] = {"Type1", "Position1", "Velocity1", "Age1"};m_updateShader = Shader("../resources/particleUpdate.vert", "../resources/particleUpdate.frag","../resources/particleUpdate.geom", feedbackVaryings);m_updateShader.use();m_updateShader.setFloat("gLauncherLifetime", 100.0f);m_updateShader.setFloat("gShellLifetime", 10000.0f);m_updateShader.setFloat("gSecondaryShellLifetime", 500.f);// 初始化 render shaderm_renderShader = Shader("../resources/particleRender.vert", "../resources/particleRender.frag","../resources/particleRender.geom");m_renderShader.use();m_renderShader.setFloat("gBillboardSize", 0.01f);// 4. 初始化 纹理// 随机数纹理m_randomTexture.id = TextureFromRand();m_randomTexture.path = "random";m_randomTexture.type = "texture_diffuse";// 粒子纹理m_particleTexture.id = TextureFromFile("particle.png", "../resources/textures/");m_particleTexture.path = "../resources/textures/particle.png";m_particleTexture.type = "texture_diffuse";return true;};...
}
c. Render()
调用 update shader 和 Render shader 进行更新粒子、渲染粒子
class ParticleSystem
{
public:
...void Render(float DeltaTimeMillis, const glm::mat4 &VP, const glm::vec3 &CameraPos){m_time += DeltaTimeMillis;// 更新 粒子updateParticles(DeltaTimeMillis);// 渲染 粒子renderParticles(VP, CameraPos);// 交换 update shader 使用的 VAO 和 TFO// 0 -> 1 -> 0 -> 1 -> 0 -> ...m_update_input_id = (m_update_input_id + 1) % 2;// 交换 render shader 使用的 VAO// 1 -> 0 -> 1 -> 0 -> 1 -> ...m_render_input_id = (m_render_input_id + 1) % 2;};...
}
d. updateParticles()
更新粒子
class ParticleSystem
{
public:
...void updateParticles(float DelatTimeMillis){// 1. 设置 update shader 中的 uniform 变量以及纹理变量m_updateShader.use();m_updateShader.setFloat("gTime", m_time);m_updateShader.setFloat("gDeltaTimeMillis", 1.0f * DelatTimeMillis);glActiveTexture(GL_TEXTURE0); // 激活纹理单元 0glUniform1i(glGetUniformLocation(m_updateShader.ID, "gRandomTexture"),0); // 将纹理单元0 与着色器的 sampler 变量 gRandomTexture 关联glBindTexture(GL_TEXTURE_1D, m_randomTexture.id); // 将 纹理对象 绑定到当前的纹理单元 GL_SAMPLER_1D 纹理上// 2. 绑定 VAO, TFB// 绑定VAO, 作为 update shader 的输入glBindVertexArray(m_VAO[m_update_input_id]);// 根据 update shader 设置 VAO 中不同属性的读取方式glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, sizeof(Particle), 0); // typeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid *)(sizeof(float))); // positionglVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Particle),(const GLvoid *)(sizeof(float) + sizeof(glm::vec3))); // velocityglVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(Particle),(const GLvoid *)((sizeof(float) + sizeof(glm::vec3)) + sizeof(glm::vec3))); // lifetimeglEnableVertexAttribArray(0);glEnableVertexAttribArray(1);glEnableVertexAttribArray(2);glEnableVertexAttribArray(3);// 绑定 TFO, 作为 update shader 的输出glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_TFO[m_render_input_id]);// 3. 开始使用 update shader 更新粒子glEnable(GL_RASTERIZER_DISCARD); // 跳过光栅化以及之后的阶段glBeginTransformFeedback(GL_POINTS);if (m_isFirst){ // 第一次 运行 update shader, 只有一个 粒子glDrawArrays(GL_POINTS, 0, 1);m_isFirst = false;}else{ // 之后运行 update shader, 粒子个数不确定, 由 opengl 根据 transform feedback object 自行确定粒子个数glDrawTransformFeedback(GL_POINTS, m_TFO[m_update_input_id]);}glEndTransformFeedback();glDisable(GL_RASTERIZER_DISCARD); // 开启光栅化以及之后的阶段glDisableVertexAttribArray(0);glDisableVertexAttribArray(1);glDisableVertexAttribArray(2);glDisableVertexAttribArray(3);};...
}
e. renderParticles()
渲染粒子
class ParticleSystem
{
public:
...void renderParticles(const glm::mat4 &VP, const glm::vec3 &CameraPos){// 1. 设置渲染状态glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT); // 使用 (0.2,0.3,0.3,1.0) 清空 color texture, 清空 depth bufferglEnable(GL_BLEND); // 启用 blendglBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blend 模式为 D = alpha*S + (1-alpha)*DglEnable(GL_PROGRAM_POINT_SIZE);// 2. 设置 render shader 中的 uniform 变量以及纹理变量m_renderShader.use();m_renderShader.setVec3("gCameraPos", CameraPos);m_renderShader.setMat4("gVP", VP);glActiveTexture(GL_TEXTURE1); // 激活纹理单元 1glUniform1i(glGetUniformLocation(m_renderShader.ID, "gColorMap"), 1);glBindTexture(GL_TEXTURE_2D, m_particleTexture.id); // 将 纹理对象 绑定到当前的纹理单元的 GL_SAMPLER_1D 纹理上// 3. 绑定 VAO// 绑定VAO, 作为 render shader 的输入glBindVertexArray(m_VAO[m_render_input_id]);// 根据 render shader 设置 VAO 中不同属性的读取方式glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (void *)(sizeof(float))); // positionglEnableVertexAttribArray(0);// 4. 开始使用 render shader 渲染粒子glDisable(GL_RASTERIZER_DISCARD); // 开启 光栅化 以及之后的阶段glDrawTransformFeedback(GL_POINTS, m_TFO[m_render_input_id]);};...
}
3.3. Update shader
a. Vertex shader
#version 410layout(location = 0) in float Type;
layout(location = 1) in vec3 Position;
layout(location = 2) in vec3 Velocity;
layout(location = 3) in float Age;out float Type0;
out vec3 Position0;
out vec3 Velocity0;
out float Age0;void main() {Type0 = Type;Position0 = Position;Velocity0 = Velocity;Age0 = Age;
}
b. Geometer shader
#version 410layout(points) in;
layout(points, max_vertices = 30) out;/* 从 vertex shader 输入的 point 的属性 */
in float Type0[];
in vec3 Position0[];
in vec3 Velocity0[];
in float Age0[];/* 输出到 fragment shader 的 point 的属性*/
out float Type1;
out vec3 Position1;
out vec3 Velocity1;
out float Age1;/* 用于更新 particle 的变量 */
uniform float gDeltaTimeMillis; // 时间间隔
uniform float gTime; // 当前时刻
uniform sampler1D gRandomTexture; // 随机纹理
uniform float gLauncherLifetime; // Launcher 的生存时间
uniform float gShellLifetime; // Shell 的生存时间
uniform float gSecondaryShellLifetime; // Secondary Shell 的生存时间#define PARTICLE_TYPE_LAUNCHER 0.0f
#define PARTICLE_TYPE_SHELL 1.0f
#define PARTICLE_TYPE_SECONDARY_SHELL 2.0f// 使用 random texture 获取一个随机值 (random texture相当于一个随机数池)
vec3 GetRandomDir(float TexCoord) {vec3 Dir = texture(gRandomTexture, TexCoord).xyz;Dir -= vec3(0.5, 0.5, 0.5);return Dir;
}void main() {// 更新 particle 的生存时间float Age = Age0[0] + gDeltaTimeMillis;// 增加随机性float g_Time = (sin(gTime) + 1.0) / 2.0 * 1000.0;g_Time = gTime;// Launcher particleif (Type0[0] == PARTICLE_TYPE_LAUNCHER) {// 如果 particle 生存时间过长// 那么就生成一个 Shell particle, 并且更新 Launcher particleif (Age >= gLauncherLifetime) {// 生成 一个 Shell particleType1 = PARTICLE_TYPE_SHELL;// 初始化 position, dir, velocity, agePosition1 = Position0[0];vec3 Dir = GetRandomDir(g_Time / 1000.0);Dir.y = max(Dir.y, 0.95);Velocity1 = normalize(Dir) / 12.0;// Velocity1 = Velocity0[0];Age1 = 0.0;// emit vertexEmitVertex();EndPrimitive();Age = 0.0;}// 更新 Launcher particleType1 = PARTICLE_TYPE_LAUNCHER;Position1 = Position0[0];Velocity1 = Velocity0[0];Age1 = Age;EmitVertex();EndPrimitive();} else {// 如果是 Shell or Second Shell particlefloat DeltaTimeSecs = gDeltaTimeMillis / 1000.0;float t1 = Age0[0] / 1000.0;float t2 = Age / 1000.0;// position 的改变量vec3 DeltaP = DeltaTimeSecs * Velocity0[0];// velocity 的改变量// vec3 DeltaV = vec3(DeltaTimeSecs) * vec3(0.0, -9.81, 0.0);// 如果是 Shell particlevec3 DeltaV = vec3(0, DeltaTimeSecs / 1000.0 * -9.81, 0);if (Type0[0] == PARTICLE_TYPE_SHELL) {if (Age < gShellLifetime) {// 如果 Shell particle 还在生存时间内Type1 = PARTICLE_TYPE_SHELL;// 更新 position, velocityPosition1 = Position0[0] + DeltaP;Velocity1 = Velocity0[0] + DeltaV;// Velocity1 = Velocity0[0];// Velocity1 = Velocity0[0] + vec3(0.0, DeltaTimeSecs * -9.8, 0.0);Age1 = Age;EmitVertex();EndPrimitive();} else {// 如果 Shell particle 超过生存时间了,那么就 分裂为 10 个 Second Shellfor (int i = 0; i < 10; i++) {Type1 = PARTICLE_TYPE_SECONDARY_SHELL;Position1 = Position0[0];vec3 Dir = GetRandomDir((g_Time + i) / 1000.0);Velocity1 = normalize(Dir) / 20.0;Age1 = 0.0f;EmitVertex();EndPrimitive();}}} else {// 如果是 Second Shell particleif (Age < gSecondaryShellLifetime) {// 如果 Second Shell 还在生存周期内Type1 = PARTICLE_TYPE_SECONDARY_SHELL;Position1 = Position0[0] + DeltaP;Velocity1 = Velocity0[0] + DeltaV;Age1 = Age;EmitVertex();EndPrimitive();}// 如果 Second Shell 超过生存周期, 那么就消灭该 Second Shell particle// (什么也不做)}}
}
c. Fragment shader
#version 410 core
void main() {// do nothing
}
3.4. Render shader
a. Vertex shader
#version 410
layout(location = 0) in vec3 Position;
void main() { gl_Position = vec4(Position, 1.0); }
b. Geometer shader
#version 410layout(points) in;
layout(triangle_strip, max_vertices = 4) out;
uniform mat4 gVP;
uniform vec3 gCameraPos;
uniform float gBillboardSize;out vec2 TexCoord;void main() {// 以 p0 = gl_Position 为右下角,绘制一个矩形 (两个三角形)// p2 --- p4// | \ |// | \ |// p1 --- p3 (p0)vec3 Pos = gl_in[0].gl_Position.xyz;vec3 toCamera = normalize(gCameraPos - Pos);vec3 up = vec3(0.0, 1.0, 0.0);vec3 right = cross(toCamera, up) * gBillboardSize;// p1Pos -= right;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(0.0, 0.0);EmitVertex();// p2Pos.y += gBillboardSize;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(0.0, 1.0);EmitVertex();// p3Pos.y -= gBillboardSize;Pos += right;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(1.0, 0.0);EmitVertex();// p4Pos.y += gBillboardSize;gl_Position = gVP * vec4(Pos, 1.0);TexCoord = vec2(1.0, 1.0);EmitVertex();EndPrimitive();
}
c. Fragment shader
#version 410uniform sampler2D gColorMap;
in vec2 TexCoord;
out vec4 FragColor;
void main() {FragColor = texture(gColorMap, TexCoord);if (FragColor.r >= 0.9 && FragColor.g >= 0.9 && FragColor.b >= 0.9) {discard;}
}
4. 全部代码及模型文件
用于实现粒子效果的全部代码以及模型文件可以在OpenGL使用TransformFeedback实现粒子效果 中下载。
三、参考引用
[1]. modern-opengl-tutorial
[2]. ogldev-tutorial28
[3]. 粒子系统–喷泉 [OpenGL-Transformfeedback]
相关文章:

[OpenGL]使用TransformFeedback实现粒子效果
一、简介 本文介绍了如何使用 OpenGL 中的 Transform Feedback 实现粒子效果,最终可以实现下图的效果: 本文的粒子系统实现参考了modern-opengl-tutorial, ogldev-tutorial28 和 粒子系统–喷泉 [OpenGL-Transformfeedback]。 二、使用 TransformFeed…...

GitCode 光引计划投稿 | GoIoT:开源分布式物联网开发平台
GoIoT 是基于Gin 的开源分布式物联网(IoT)开发平台,用于快速开发,部署物联设备接入项目,是一套涵盖数据生产、数据使用和数据展示的解决方案。 GoIoT 开发平台,它是一个企业级物联网平台解决方案ÿ…...

用 gdbserver 调试 arm-linux 上的 AWTK 应用程序
很多嵌入式 linux 开发者都能熟练的使用 gdb/lldb 调试应用程序,但是还有不少朋友在调试开发板上的程序时,仍然在使用原始的 printf。本文介绍一下使用 gdbserver 通过网络调试开发板上的 AWTK 应用程序的方法,供有需要的朋友参考。 1. 下载 …...

攻防世界web第一题
最近开始学习网络安全的相关知识,开启刷题,当前第一题 题目为攻防世界web新手题 这是题目 翻译:在这个训练挑战中,您将了解 Robots_exclusion_standard。网络爬虫使用 robots.txt 文件来检查是否允许它们对您的网站或仅网站的一部…...

轮播图带详情插件,插件
超级好用的轮播图 介绍访问地址参数介绍使用方法(简单使用,参数结构点击链接查看详情)图片展示 介绍 video(15) 带有底部物品介绍以及价格的轮播图组件,持续维护,uniApp插件,直接下载填充数据就可以在项目里…...

gesp(三级)(14)洛谷:B4039:[GESP202409 三级] 回文拼接
gesp(三级)(14)洛谷:B4039:[GESP202409 三级] 回文拼接 题目描述 一个字符串是回文串,当且仅当该字符串从前往后读和从后往前读是一样的,例如, aabaa \texttt{aabaa} aabaa 和...

ISO17025最新认证消息
ISO17025认证是国际上广泛认可的实验室管理标准,全称为《检测和校准实验室能力的通用要求》,由国际标准化组织(ISO)和国际电工委员会(IEC)联合发布。以下是对ISO17025最新认证消息及相关内容的归纳…...

ASP.NET Core Web API 控制器
文章目录 一、基类:ControllerBase二、API 控制器类属性三、使用 Get() 方法提供天气预报结果 在深入探讨如何编写自己的 PizzaController 类之前,让我们先看一下 WeatherController 示例中的代码,了解它的工作原理。 在本单元中,…...

RAID5原理简介和相关问题
1、RAID5工作原理 2、RAID5单块硬盘的数据连续吗? 3、RAID5单块硬盘存储的是原始数据,还是异或后的数据? 4、RAID5的分块大小 RAID5的分块大小一般选择4KB到64KB之间较为合适。选择合适的分块大小主要取决于以下几个考量因素࿱…...

Axure RP 8安装(内带安装包)
通过网盘分享的文件:Axure8.0.zip 链接: https://pan.baidu.com/s/195_qy2iiDIcYG4puAudScA 提取码: 6xt8 --来自百度网盘超级会员v1的分享 勾选I Agree 安装完成...

stm32定时器输出比较----驱动步进电机
定时器输出比较理论 OC(Output Compare)输出比较输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形每个高级定时器和通用定时器都拥有4个输出比较通道高级定时器的前3个通道额外拥有死区生成和互补输出…...

关于鸿蒙架构feature
鸿蒙feature层模块架构 model:定义数据类型,进行接口请求 view:视图层 写UI viewModel:控制层 关于逻辑和请求调用 page页...

【递归,搜索与回溯算法 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(一)
找出所有子集的异或总和再求和 题目解析 算法原理 解法 决策树 这种决策使得每一次递归都是有效的递归,每一个节点都是最终的结果,所以这棵决策树是不用剪枝的,也没有递归出口的; 注意 决策树执行添加元素…...

vue3 如何使用 mounted
vue3 如何使用 mounted 在 Vue 3 中,mounted 生命周期钩子用于当组件被挂载到 DOM 中后执行一些操作。 这个钩子非常适合用来执行那些依赖于 DOM 的初始化工作,比如获取元素的尺寸或者是与第三方的 DOM 有关的库进行交互等。 下面是一个简单的 Vue 3 组…...

PostgreSQL JOIN
PostgreSQL中的JOIN操作是一种用于合并两个或多个表的SQL语句,它允许根据某些条件(通常是表之间的外键关系)将相关的数据组合在一起。PostgreSQL支持多种类型的JOIN,包括: CROSS JOIN(交叉连接)…...

mysql(基础语法)
准备一张员工表 /*Navicat Premium Data TransferSource Server : localhost_3306Source Server Type : MySQLSource Server Version : 80037 (8.0.37)Source Host : localhost:3306Source Schema : studymysqlTarget Server Type : MySQLTar…...

【论文阅读笔记】Scalable, Detailed and Mask-Free Universal Photometric Stereo
【论文阅读笔记】Scalable, Detailed and Mask-Free Universal Photometric Stereo 前言摘要引言Task 相关工作方法SDM-UniPS预处理尺度不变的空间光特征编码器像素采样变压器的非局部交互 PS-Mix数据集 实验结果训练细节评估和时间: 消融实验定向照明下的评估没有对…...

抓取手机HCI日志
荣耀手机 1、打开开发者模式 2、开启HCI、ADB调试 3、开启AP LOG 拨号界面输入*##2846579##* 4、蓝牙配对 5、抓取log adb pull /data/log/bt ./...

【linux】 unshare -user -r /bin/bash命令详解
命令解析 unshare -user -r /bin/bash 是一个 Linux 命令,它用于在新的用户命名空间中运行一个进程(在这个例子中是 /bin/bash)。以下是这个命令的详细解释: 【1. 命令解析】 unshare: unshare 是一个工具,用于从调用…...

微软远程桌面APP怎么用
微软远程桌面(Remote Desktop)客户端(RD Client)是一款由微软开发的应用程序,允许用户通过网络连接远程访问和控制另一台计算机。同时,微软远程桌面RD Client支持多种设备和操作系统,包括Window…...

Android9.x SurfaceView源码分析
前言 本文是继Android 深入理解SurfaceView再次对SurfaceView进行源码分析。 看了下代码,上篇文章是基于Android7.x的,本篇基于Android9.x再次进行分析, Android从7.0开始支持SurfaceView动画,并建议7.0之后使用SurfaceView替代TextureView,这里主要在Android9.0上分析Su…...

MDS-NPV/NPIV
在存储区域网络(SAN)中,域ID(Domain ID)是一个用于区分不同存储区域的关键参数。域ID允许SAN环境中的不同部分独立操作,从而提高效率和安全性。以下是关于域ID的一些关键信息: 域ID的作用&…...

通用人工智能的关键:统一语言描述万物
当今世界,人工智能(AI)正以前所未有的速度推进着人类社会的进步。从最初的简单计算到如今能够执行复杂任务的智能系统,AI 的每一次飞跃都伴随着理解世界能力的显著提升。然而,要实现真正的通用人工智能——即能够像人类…...

JSON 系列之1:将 JSON 数据存储在 Oracle 数据库中
本文为Oracle数据库JSON学习系列的第一篇,讲述如何将JSON文档存储到数据库中,包括了版本为19c和23ai的情形。 19c中的JSON 先来看一下数据库版本为19c时的情形。 创建表colortab,其中color列的长度设为4000。若color的长度需要设为32767&a…...

[前端]HTTP库Axios
一、Axios简介 Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 node.js 环境。它是一个流行的 JavaScript 库,用于发起 HTTP 请求,如 GET、POST、DELETE 等。Axios 提供了易于使用的 API,支持请求和响应的拦截、转换数据格…...

vue3入门教程:reactive函数
基本用法 引入 reactive 首先,你需要从 vue 包中引入 reactive 函数: import { reactive } from vue;创建一个响应式对象 使用 reactive 函数来创建一个响应式对象: const state reactive({count: 0,name: Vue 3 });在这个例子中,…...

SDMTSP:黑翅鸢算法(Black-winged kite algorithm,BKA)求解单仓库多旅行商问题,可以更改数据集和起点(MATLAB代码)
一、黑翅鸢算法BKA 黑翅鸢算法(Black-winged kite algorithm,BKA)由Wang Jun等人于2024年提出,该算法受黑翅鸢的迁徙和掠食行为启发而得。BKA集成了柯西突变策略和领导者策略,增强了算法的全局搜索能力,提…...

叉车作业如何确认安全距离——UWB测距防撞系统的应用
叉车在工业环境中运行,常常需要在狭窄的空间内完成货物的搬运和堆垛,这对操作员的技术水平和安全意识提出了极高的要求。传统的叉车作业依赖操作员的经验和视觉判断来确认安全距离,然而这种方式往往存在误差,特别是在视线受阻或光…...

5-Gin 静态文件服务 --[Gin 框架入门精讲与实战案例]
在使用 Gin 框架开发 Go 语言应用程序时,提供静态文件服务(如 HTML、CSS、JavaScript 文件等)是一个常见的需求。Gin 提供了简单的方法来设置静态文件的路由,使得你可以轻松地将这些资源提供给客户端。 使用 Static 方法 最直接…...

【自动驾驶】3 激光雷达③
5 激光雷达点云检测模型 🦋🦋🦋CenterPoint是Anchor‐Free的3D物体检测器,以点云作为输入,将三维物体在Bird‐View下的中心点作为关键点,基于关键点检测的方式回归物体的尺寸、方向和速度。相比于Anchor‐…...