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

NDK OpenGL离屏渲染与工程代码整合

NDK​系列之OpenGL离屏渲染与工程代码整合,本节主要是对上一节OpenGL渲染画面效果代码进行封装设计,将各种特效代码进行分离解耦,便于后期增加其他特效。

实现效果:

实现逻辑:

1.封装BaseFilter过滤器基类,实现通用的顶点着色器、片元着色器和着色器程序的初始化,赋值和绘制操作;

2.编写CamreaFilter相机过滤器,实现FBO离屏渲染和变换矩阵等相机相关操作,绘制操作完成后,将相机过滤器实现的效果的纹理ID传递给下一个过滤器,在相机过滤器效果上进行叠加其他效果。

本节主要内容:

1.封装BaseFilter过滤器基类;

2.实现CamreaFilter相机过滤器;

3.实现ScreenFilter屏幕过滤器;

4.自定义渲染器MyGlRenderer;

源码:

NdkOpenGLPlay: NDK OpenGL渲染画面效果

一、封装BaseFilter过滤器基类

1)BaseFilter构造方法,保存顶点着色器和片元着色器代码资源文件id,保存坐标系到数据缓冲区;

public BaseFilter(Context context, int vertexSourceId, int fragmentSourceId) {this.mVertexSourceId = vertexSourceId; // 子类传递过来的顶点着色器代码IDthis.mFragmentSourceId = fragmentSourceId; // 子类传递过来的片元着色器代码ID// 顶点相关 坐标系float[] VERTEX = {-1.0f, -1.0f,1.0f, -1.0f,-1.0f, 1.0f,1.0f, 1.0f,};mVertexBuffer = BufferHelper.getFloatBuffer(VERTEX); // 保存到 顶点坐标数据缓冲区// 纹理相关 坐标系float[] TEXTURE = {0.0f, 0.0f,1.0f, 0.0f,0.0f, 1.0f,1.0f, 1.0f,};mTextureBuffer = BufferHelper.getFloatBuffer(TEXTURE); // 保存到 纹理坐标数据缓冲区init(context);
}

2)加载并编译着色器代码,链接色器ID,输出着色器程序,并获取着色器的索引值;

private void init(Context context) {String vertexSource = TextResourceReader.readTextFileFromResource(context, mVertexSourceId); // 顶点着色器代码字符串String fragmentSource = TextResourceReader.readTextFileFromResource(context, mFragmentSourceId); // 片元着色器代码字符串int vertexShaderId = ShaderHelper.compileVertexShader(vertexSource); // 编译顶点着色器代码字符串int fragmentShaderId = ShaderHelper.compileFragmentShader(fragmentSource); // 编译片元着色器代码字符串mProgramId = ShaderHelper.linkProgram(vertexShaderId, fragmentShaderId); // 链接 顶点着色器ID,片元着色器ID 最终输出着色器程序// 删除 顶点 片元 着色器IDglDeleteShader(vertexShaderId);glDeleteShader(fragmentShaderId);// 顶点着色器里面的如下:vPosition = glGetAttribLocation(mProgramId, "vPosition"); // 顶点着色器:的索引值vCoord = glGetAttribLocation(mProgramId, "vCoord"); // 顶点着色器:纹理坐标,采样器采样图片的坐标 的索引值vMatrix = glGetUniformLocation(mProgramId, "vMatrix"); // 顶点着色器:变换矩阵 的索引值// 片元着色器里面的如下:vTexture = glGetUniformLocation(mProgramId, "vTexture"); // 片元着色器:采样器
}

3)完成绘制操作,顶点坐标赋值,纹理坐标赋值,采样器赋值,通知opengl绘制,将过滤器实现的效果的纹理ID返回给下一个过滤器;

public int onDrawFrame(int textureId) {// 设置视窗大小glViewport(0, 0, mWidth, mHeight);glUseProgram(mProgramId); // 必须要使用着色器程序一次// TODO 画画,绘制 等工作// TODO 1.顶点坐标赋值mVertexBuffer.position(0);// 传值glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer);// 激活glEnableVertexAttribArray(vPosition);// TODO 2.纹理坐标赋值mTextureBuffer.position(0);// 传值glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer);// 激活glEnableVertexAttribArray(vCoord);// 只需要把OpenGL的纹理ID,渲染到屏幕上就可以了,不需要矩阵数据传递给顶点着色器了// 变换矩阵 把mtx矩阵数据 传递到 vMatrix// glUniformMatrix4fv(vMatrix, 1, false, mtx, 0);// TODO 3.片元 vTextureglActiveTexture(GL_TEXTURE0); // 激活图层// 不需要关心摄像头 和 矩阵// 绑定图层,为什么不需要GL_TEXTURE_EXTERNAL_OES?答:目前拿到的textureId已经是纹理ID了,不是摄像头直接采集到的纹理IDglBindTexture(GL_TEXTURE_2D ,textureId);// 因为CameraFilter已经做过了,我就直接显示,我用OepnGL 2D GL_TEXTURE_2D 显示就行了// glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); // 由于这种方式并不是通用的,所以先去除glUniform1i(vTexture, 0); // 传递采样器glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 通知 opengl 绘制return textureId; // 返回纹理ID,可以告诉下一个过滤器
}

二、实现CamreaFilter相机过滤器

1)CameraFilter构造方法,将顶点着色器和片元着色器代码资源文件id传给父类,完成顶点着色器、片元着色器和着色器程序的初始化工作;

public CameraFilter(Context context) {super(context, R.raw.camera_vertex, R.raw.camera_fragment);
}

2)准备工作,创建FBO帧缓冲区和FBO的纹理ID,并绑定起来;

public void onReady(int width, int height) {super.onReady(width,height);// TODO 准备工作// TODO 第一步:创建 FBO (看不见的离屏的屏幕)mFrameBuffers = new int[1];// 参数1:int n, fbo 个数// 参数2:int[] framebuffers, 用来保存 fbo id 的数组// 参数3:int offset 从数组中第几个id来保存,从零下标开始glGenFramebuffers(mFrameBuffers.length, mFrameBuffers, 0); // 实例化创建帧缓冲区,FBO缓冲区// TODO 第二步:创建属于 fbo 纹理(第一节课是没有配置的,但是这个是FOB纹理,所以需要配置纹理)// 既然上面的 FBO(看不见的离屏的屏幕),下面的目的就是要把画面显示到FBO中mFrameBufferTextures = new int[1]; // 记录FBO纹理的IDTextureHelper.genTextures(mFrameBufferTextures); // 生成并配置纹理// TODO 第三步:上面的 FBO缓冲区 与 FBO纹理 还没有任何关系,现在要让他们绑定起来glBindTexture(GL_TEXTURE_2D, mFrameBufferTextures[0]);// 生产2D纹理图像/*int target,         要绑定的纹理目标int level,          level一般都是0int internalformat, 纹理图像内部处理的格式是什么,rgbaint width,          宽int height,         高int border,         边界int format,         纹理图像格式是什么,rgbaint type,           无符号字节的类型java.nio.Buffer pixels*/glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null);glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers[0]); // 发生关系/*int target,     fbo的纹理目标int attachment, 附属到哪里int textarget,  要绑定的纹理目标int texture,    纹理int level       level一般都是0*/glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mFrameBufferTextures[0], 0);// TODO 第四步:解绑操作glBindTexture(GL_TEXTURE_2D, 0);glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

3)完成绘制操作,渲染到FBO离线缓存中,顶点坐标赋值,纹理坐标赋值,变换矩阵把mtx矩阵数据传递到vMatrix,相机过滤器处理过后,其他过滤器不需要再变换矩阵,采样器赋值,通知opengl绘制,将FBO的纹理ID返回给下一个过滤器;

public int onDrawFrame(int textureId) {// 不能调用super,因为父类做的事情,和子类是很大区别的// super.onDrawFrame(textureId);glViewport(0, 0, mWidth, mHeight);  // 设置视窗大小// TODO 渲染到 FBO离线缓存中// 绑定FBO缓存(否则会绘制到屏幕上) 我们最终的效果是 离屏渲染glBindFramebuffer(GL_FRAMEBUFFER, mFrameBuffers[0]);glUseProgram(mProgramId); // 必须要使用着色器程序一次// 画画 绘制操作mVertexBuffer.position(0); // 顶点坐标赋值glVertexAttribPointer(vPosition, 2, GL_FLOAT, false, 0, mVertexBuffer); // 传值glEnableVertexAttribArray(vPosition); // 激活mTextureBuffer.position(0); // 纹理坐标赋值glVertexAttribPointer(vCoord, 2, GL_FLOAT, false, 0, mTextureBuffer); // 传值glEnableVertexAttribArray(vCoord); // 激活// TODO 变换矩阵,在我们CameraFilter这里就需要处理了,后面的BaseFilter就不需要了glUniformMatrix4fv(vMatrix, 1, false, matrix, 0);// 片元 vTextureglActiveTexture(GL_TEXTURE0); // 激活图层// glBindTexture(GL_TEXTURE_2D ,textureId); // 公用的那个  1glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); // 摄像头打交道采样器:使用额外拓展的,不能使用公用的那个  2glUniform1i(vTexture, 0);glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // 通知 opengl 绘制// 解绑 fboglBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);glBindFramebuffer(GL_FRAMEBUFFER, 0);// FBO的纹理ID,返回了return mFrameBufferTextures[0]; // 你没有学过C,你就不明白,如果你学过,无需多言
}

4)相机过滤器顶点着色器代码:

attribute vec4 vPosition; // 顶点坐标,相当于:相机的四个点位置排版attribute vec4 vCoord; // 纹理坐标,用来图形上色的uniform mat4 vMatrix; // 变换矩阵,4*4的格式的varying vec2 aCoord; // 把这个最终的计算成果,给片元着色器 【不需要Java传递,他是计算出来的】void main() {gl_Position = vPosition; // 确定好位置排版aCoord = (vMatrix * vCoord).xy; // 兼容所有设备
}

5)相机过滤器片元着色器代码:

// 导入 samplerExternalOES 
#extension GL_OES_EGL_image_external : require// float 数据的精度 (precision lowp = 低精度) (precision mediump = 中精度) (precision highp = 高精度)
precision mediump float;// 根据上面的数据的精度,写下面的 采样器 相机的数据
// uniform sampler2D vTexture;  由于我们用的是 安卓的相机,就不能用他
uniform samplerExternalOES vTexture; // samplerExternalOES才能采样相机的数据varying vec2 aCoord; // 把这个最终的计算成果,给片元着色器,拿到最终的成果,我才能上色void main() {// 底片效果vec4 rgba = texture2D(vTexture,aCoord);  // rgbagl_FragColor = vec4(1.-rgba.r, 1.-rgba.g, 1.-rgba.b, rgba.a);
}

三、实现ScreenFilter屏幕过滤器

1)ScreenFilter构造方法,将顶点着色器和片元着色器代码资源文件id传给父类,完成顶点着色器、片元着色器和着色器程序的初始化工作;

public ScreenFilter(Context context) {super(context, R.raw.base_vertex, R.raw.base_fragment); // base_vertex(没有矩阵) base_fragment(没有OES 是sampler2D)
}

2)准备工作和绘制操作复用父类完成;

3)屏幕过滤器顶点着色器代码:

attribute vec4 vPosition; // 顶点坐标,相当于:相机的四个点位置排版attribute vec2 vCoord; // 纹理坐标,用来图形上色的varying vec2 aCoord; // 把这个最终的计算成果,给片元着色器 【不需要Java传递,他是计算出来的】void main() {gl_Position = vPosition; // 确定好位置排版   gl_Position OpenGL着色器语言内置的变量// 着色器语言基础语法// aCoord = vCoord.xy;// aCoord是2个分量的    vCoord是四个分量的.xy取出两个分量aCoord = vCoord;
}

4)屏幕过滤器片元着色器代码

// float 数据的精度 (precision lowp = 低精度) (precision mediump = 中精度) (precision highp = 高精度)
precision mediump float;// 根据上面的数据的精度,写下面的 采样器 相机的数据
uniform sampler2D vTexture;// 由于我们用的是 安卓的相机,就不能用他(用OpenGL 2D显示就行了,不需要摄像头了,sampler2D)varying vec2 aCoord;// 把这个最终的计算成果,给片元着色器,拿到最终的成果,我才能上色void main() {// texture2D (采样器, 坐标)  gl_FragColor OpenGL着色器语言内置的变量gl_FragColor = texture2D(vTexture, aCoord);// 直接上色,你直接上色,是上camera_fragment.glsl的底片效果
}

四、自定义渲染器MyGlRenderer

从上一节中我们知道,MyGlRenderer实现GLSurfaceView.Renderer接口,实现接口的onSurfaceCreated()、onSurfaceChanged()和onDrawFrame()方法;

1)当Surface创建时,回调onSurfaceCreated()函数,创建ScreenFilter屏幕过滤器,同样在创建ScreenFilter屏幕过滤器之前,我们要先创建CamreaFilter相机过滤器;

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {mCameraFilter = new CameraFilter(myGLSurfaceView.getContext()); // 先 FBOmScreenFilter = new ScreenFilter(myGLSurfaceView.getContext()); // 后 渲染屏幕
}

2)当Surface改变时,回调onSurfaceChanged()函数,完成过滤器准备工作;

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {mCameraFilter.onReady(width, height);mScreenFilter.onReady(width, height);
}

3)绘制一帧图像时,回调onDrawFrame()函数,获取纹理对象的图像数据,先通过CamreaFilter相机过滤器,实现相关效果,再将其FBO的纹理ID传递给下一个过滤器,这里由于暂无实现其他特效,就传递给了ScreenFilter屏幕过滤器,将最终成果的纹理ID通过OpenGL渲染到屏幕;

@Override
public void onDrawFrame(GL10 gl) {// 相机过滤器,绘制一帧图像,不可见mCameraFilter.setMatrix(mtx);int textureId = mCameraFilter.onDrawFrame(mTextureID[0]); // 摄像头,矩阵,都已经做了// 增加其他特效/*textureId = 美白.onDrawFrame(textureId);textureId = 大眼.onDrawFrame(textureId);textureId = xxx.onDrawFrame(textureId);*/// 屏幕过滤器,绘制一帧图像,屏幕显示mScreenFilter.onDrawFrame(textureId); // textureId == 最终成果的纹理ID
}

注:后续将会在CamreaFilter相机过滤器和ScreenFilter屏幕过滤器中间增加各种效果过滤器,实现其他特效,如:美白,大眼,兔耳朵等。

至此,OpenGL离屏渲染与工程代码整合已完成。

源码:

NdkOpenGLPlay: NDK OpenGL渲染画面效果

相关文章:

NDK OpenGL离屏渲染与工程代码整合

NDK​系列之OpenGL离屏渲染与工程代码整合,本节主要是对上一节OpenGL渲染画面效果代码进行封装设计,将各种特效代码进行分离解耦,便于后期增加其他特效。 实现效果: 实现逻辑: 1.封装BaseFilter过滤器基类&#xff0c…...

Python基础入门编程代码练习(二)

一、求1~100之间不能被3整除的数之和 循环条件&#xff1a;i<100循环操作 实现代码如下&#xff1a; def sums():sum 0for num in range(1, 101):if num % 3 ! 0:sum numprint("1~100之间不能被3整除的数之和为&#xff1a;%s" % (sum))sums() print("1~…...

C# | 对象池

对象池 文章目录 对象池前言什么是对象池对象池的优点对象池的缺点 实现思路示例代码 结束语 前言 当我们开发一个系统或者应用程序时&#xff0c;我们通常需要创建很多的对象&#xff0c;这些对象可能是线程、内存、数据库连接、文件句柄等等。在某些情况下&#xff0c;我们需…...

CSS小技巧之圆形虚线边框

虚线相信大家日常都用的比较多&#xff0c;常见的用法就是使用 border-style 控制不同的样式&#xff0c;比如设置如下边框代码&#xff1a; border-style: dotted dashed solid double;这将设置顶部的边框样式为点状&#xff0c;右边的边框样式为虚线&#xff0c;底部的边框样…...

QString与QByteArray互相转换的方法

QString与QByteArray互相转换的方法 [1] QString与QByteArray互相转换的方法QString转QByteArray方法QByteArray转QString方法QByteArray类同样不以’\0’为结尾QByteArray转QString&#xff0c;主要用buf.toHex()即可 [2] Qt开发串口通讯软件中的数据转换问题1.读取串口命令-Q…...

Springboot +Flowable,设置流程变量的方式(一)

一.简介 为什么需要流程变量。 举个例子&#xff0c;假设有如下一个流程&#xff0c;截图如下&#xff1a; 这是一个请假流程&#xff0c;那么谁请假、请几天、起始时间、请假理由等等&#xff0c;这些都需要说明&#xff0c;不然领导审批的依据是啥&#xff1f;那么如何传递…...

机器学习13(正则化)

文章目录 简介正则化经验风险和结构风险过拟合正则化建模策略 逻辑回归逻辑回归评估器 练习评估器训练与过拟合实验评估器的手动调参 简介 这一节详细探讨关于正则化的相关内容&#xff0c;并就 sklearn 中逻辑回归&#xff08;评估器&#xff09;的参数进行详细解释由于 skle…...

并发编程学习(十一):原子数组、

1、数组类型的原子类 原子数组类型&#xff0c;这个其实和AtomicInteger等类似&#xff0c;只不过在修改时需要指明数组下标。 CAS是按照来根据地址进行比较。数组比较地址&#xff0c;肯定是不行的&#xff0c;只能比较下标元素。而比较下标元素&#xff0c;就和元素的…...

递归到动态规划:省去枚举行为

如果在动态规划的过程中没有枚举行为&#xff0c;那严格位置依赖和傻缓存的方式并没有太大区别&#xff0c;但是当有枚举行为的时候&#xff08;一个位置依赖于多个位置&#xff09;&#xff0c;那严格位置依赖是有优化空间的&#xff0c;枚举行为也许可以省去&#xff0c;题目…...

服务(第二十一篇)mysql高级查询语句(二)

①视图表&#xff1a; 视图表是虚拟表&#xff0c;用来存储SQL语句的定义 如果视图表和原表的字段相同&#xff0c;是可以进行数据修改的&#xff1b; 如果两者的字段不通&#xff0c;不可以修改数据。 语法&#xff1a; 创建&#xff1a;create view 试图表名 as ... 查…...

MYSQL高可用配置(MHA)

1、什么是MHA MHA&#xff08;Master High Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作。 MHA能在故障切换的过程中最大…...

单精度浮点数与十进制数据相互转换

一、float基础&#xff1a; Float类型占4个字节,也就是32bit,其中最高位是符号位,2~9位是指数位,后边的23bit是数值位.如下所示 大部分数据的二进制形式都可以用科学计数法表示,即1.m*2^n这种形式,只要知道m和n,就能确定一个数值 二、小数位如何转变为二进制&#xff1a; 下面…...

PMP敏捷-4大价值观、12原则

宣言及4大价值观 个体及互动 胜于 流程和工具 以人为本 工作的软件 胜于 完整的文档 以价值为导向 客户合作 胜于 合同谈判 合作共赢 应对变更 胜于 遵循计划 拥抱变化 12原则 工作原则&#xff1a;精益、至简&#xff0c;实现这种原则的方式是“定期反省”。9、10、12 …...

K8S—Helm

一、Helm介绍 helm通过打包的方式&#xff0c;支持发布的版本管理和控制&#xff0c;很大程度上简化了Kubernetes应用的部署和管理。 Helm本质就是让k8s的应用管理&#xff08;Deployment、Service等&#xff09;可配置&#xff0c;能动态生成。通过动态生成K8S资源清单文件&a…...

ALSA内部函数调用流程

ALSA内部函数调用流程 一直都有这样的一个疑问 就是在linux系统中我们调用snd_pcm_open后&#xff0c;就不知道alsa内部是怎么运行的了 用户的pcm_open()相当于先对ASoC各个驱动模块startup()&#xff0c;再做hw_params()。 pcm_open()pcm->fd open("/dev/snd/pcm…...

Python正则表达式详解,保姆式教学,0基础也能掌握正则

正则作为处理字符串的一个实用工具&#xff0c;在Python中经常会用到&#xff0c;比如爬虫爬取数据时常用正则来检索字符串等等。正则表达式已经内嵌在Python中&#xff0c;通过导入re模块就可以使用&#xff0c;作为刚学Python的新手大多数都听说”正则“这个术语。 今天来给…...

ChatGPT 接入飞书教程,创建自己的聊天机器人

ChatGPT 接入飞书教程,创建自己的聊天机器人 一、飞书进入开发者平台。点击创建应用。二、打开Aircode,点击创建应用,上面输入名字,下面选择Node.js v16三、配置环境,点击Environments,创建四个变量,全部要大写本教程收集于: AIGC从入门到精通教程 首先,准备三个账号…...

JS生成随机数(多种解决方案)

JS生成随机数 概述 随机数是编程语言中的重要组成部分。在JavaScript中&#xff0c;生成随机数是一项简单的任务。本文将介绍生成随机数的各种方法。 Math.random() Math.random()是JavaScript中生成随机数最常见的方法。该方法返回介于0和1之间的随机数。例如&#xff0c;…...

文件IO 函数 静态库和动态库的创建 5.11

5.11 文件IO函数 1.数据读写 ssize_t read(int fd,void *buf,size_t count); 功能&#xff1a; ​ 从fd对应的文件中 读取前count个字节的数据到buf缓冲区中 头文件&#xff1a; ​ #include <unistd.h> 参数&#xff1a; ​ fd &#xff1a;文件描述符 ​ buf…...

考研日语-详解ている、てある、ていく、てくる用法

目录 一、ている用法 1. 表示现在状态 2. 表示持续动作 3. 表示经验或习惯 4. 表示结果或效果 二、てある用法 1. 表示已经完成的动作 2. 表示现在状态 3. 表示被动 三、ていく用法 1. 表示未来的动作 2. 表示逐渐变化的过程 四、てくる用法 1. 表示过去到现在的…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机

这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机&#xff0c;因为在使用过程中发现 Airsim 对外部监控相机的描述模糊&#xff0c;而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置&#xff0c;最后在源码示例中找到了&#xff0c;所以感…...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...