当前位置: 首页 > news >正文

[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 VBOUpdate output VBO 不能是同一个 buffer object。因此在代码实现使用两个 VBO 交替作为 一个Update-Render流程中的Update input VBO Update output VBO
例如,渲染一个n帧的结果,其 Update input VBOUpdate 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 实现粒子效果&#xff0c;最终可以实现下图的效果&#xff1a; 本文的粒子系统实现参考了modern-opengl-tutorial, ogldev-tutorial28 和 粒子系统–喷泉 [OpenGL-Transformfeedback]。 二、使用 TransformFeed…...

GitCode 光引计划投稿 | GoIoT:开源分布式物联网开发平台

GoIoT 是基于Gin 的开源分布式物联网&#xff08;IoT&#xff09;开发平台&#xff0c;用于快速开发&#xff0c;部署物联设备接入项目&#xff0c;是一套涵盖数据生产、数据使用和数据展示的解决方案。 GoIoT 开发平台&#xff0c;它是一个企业级物联网平台解决方案&#xff…...

用 gdbserver 调试 arm-linux 上的 AWTK 应用程序

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

攻防世界web第一题

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

轮播图带详情插件,插件

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

gesp(三级)(14)洛谷:B4039:[GESP202409 三级] 回文拼接

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

ISO17025最新认证消息

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

ASP.NET Core Web API 控制器

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

RAID5原理简介和相关问题

1、RAID5工作原理 2、RAID5单块硬盘的数据连续吗&#xff1f; 3、RAID5单块硬盘存储的是原始数据&#xff0c;还是异或后的数据&#xff1f; 4、RAID5的分块大小 ‌RAID5的分块大小一般选择4KB到64KB之间较为合适‌。选择合适的分块大小主要取决于以下几个考量因素&#xff1…...

Axure RP 8安装(内带安装包)

通过网盘分享的文件&#xff1a;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&#xff1a;定义数据类型&#xff0c;进行接口请求 view&#xff1a;视图层 写UI viewModel&#xff1a;控制层 关于逻辑和请求调用 page页...

【递归,搜索与回溯算法 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(一)

找出所有子集的异或总和再求和 题目解析 算法原理 解法 决策树 这种决策使得每一次递归都是有效的递归&#xff0c;每一个节点都是最终的结果&#xff0c;所以这棵决策树是不用剪枝的&#xff0c;也没有递归出口的&#xff1b; 注意 决策树执行添加元素…...

vue3 如何使用 mounted

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

PostgreSQL JOIN

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

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数据集 实验结果训练细节评估和时间&#xff1a; 消融实验定向照明下的评估没有对…...

抓取手机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 命令&#xff0c;它用于在新的用户命名空间中运行一个进程&#xff08;在这个例子中是 /bin/bash&#xff09;。以下是这个命令的详细解释&#xff1a; 【1. 命令解析】 unshare: unshare 是一个工具&#xff0c;用于从调用…...

微软远程桌面APP怎么用

微软远程桌面&#xff08;Remote Desktop&#xff09;客户端&#xff08;RD Client&#xff09;是一款由微软开发的应用程序&#xff0c;允许用户通过网络连接远程访问和控制另一台计算机。同时&#xff0c;微软远程桌面RD Client支持多种设备和操作系统&#xff0c;包括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

在存储区域网络&#xff08;SAN&#xff09;中&#xff0c;域ID&#xff08;Domain ID&#xff09;是一个用于区分不同存储区域的关键参数。域ID允许SAN环境中的不同部分独立操作&#xff0c;从而提高效率和安全性。以下是关于域ID的一些关键信息&#xff1a; 域ID的作用&…...

通用人工智能的关键:统一语言描述万物

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

JSON 系列之1:将 JSON 数据存储在 Oracle 数据库中

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

[前端]HTTP库Axios

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

vue3入门教程:reactive函数

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

SDMTSP:黑翅鸢算法(Black-winged kite algorithm,BKA)求解单仓库多旅行商问题,可以更改数据集和起点(MATLAB代码)

一、黑翅鸢算法BKA 黑翅鸢算法&#xff08;Black-winged kite algorithm&#xff0c;BKA&#xff09;由Wang Jun等人于2024年提出&#xff0c;该算法受黑翅鸢的迁徙和掠食行为启发而得。BKA集成了柯西突变策略和领导者策略&#xff0c;增强了算法的全局搜索能力&#xff0c;提…...

叉车作业如何确认安全距离——UWB测距防撞系统的应用

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

5-Gin 静态文件服务 --[Gin 框架入门精讲与实战案例]

在使用 Gin 框架开发 Go 语言应用程序时&#xff0c;提供静态文件服务&#xff08;如 HTML、CSS、JavaScript 文件等&#xff09;是一个常见的需求。Gin 提供了简单的方法来设置静态文件的路由&#xff0c;使得你可以轻松地将这些资源提供给客户端。 使用 Static 方法 最直接…...

【自动驾驶】3 激光雷达③

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