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过滤器基类,…...

Python基础入门编程代码练习(二)
一、求1~100之间不能被3整除的数之和 循环条件:i<100循环操作 实现代码如下: def sums():sum 0for num in range(1, 101):if num % 3 ! 0:sum numprint("1~100之间不能被3整除的数之和为:%s" % (sum))sums() print("1~…...

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

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

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

Springboot +Flowable,设置流程变量的方式(一)
一.简介 为什么需要流程变量。 举个例子,假设有如下一个流程,截图如下: 这是一个请假流程,那么谁请假、请几天、起始时间、请假理由等等,这些都需要说明,不然领导审批的依据是啥?那么如何传递…...

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

并发编程学习(十一):原子数组、
1、数组类型的原子类 原子数组类型,这个其实和AtomicInteger等类似,只不过在修改时需要指明数组下标。 CAS是按照来根据地址进行比较。数组比较地址,肯定是不行的,只能比较下标元素。而比较下标元素,就和元素的…...

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

服务(第二十一篇)mysql高级查询语句(二)
①视图表: 视图表是虚拟表,用来存储SQL语句的定义 如果视图表和原表的字段相同,是可以进行数据修改的; 如果两者的字段不通,不可以修改数据。 语法: 创建:create view 试图表名 as ... 查…...

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

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

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

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

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

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

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

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

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

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

Spring Security 6.x 系列【36】授权服务器篇之OpenID Connect 1.0
有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.0.4 本系列Spring Security 版本 6.0.2 本系列Spring Authorization Server 版本 1.0.2 源码地址:https://gitee.com/pearl-organization/study-spring-security-demo 文章目录 1. 前言2. OpenID Connect…...

【计算机视觉 | Pytorch】timm 包的具体介绍和图像分类案例(含源代码)
一、具体介绍 timm 是一个 PyTorch 原生实现的计算机视觉模型库。它提供了预训练模型和各种网络组件,可以用于各种计算机视觉任务,例如图像分类、物体检测、语义分割等等。 timm 的特点如下: PyTorch 原生实现:timm 的实现方式…...

轻博客Plume的搭建
什么是 Plume ? Plume 是一个基于 ActivityPub 的联合博客引擎。它是用 Rust 编写的,带有 Rocket 框架,以及 Diesel 与数据库交互。前端使用 Ructe模板、WASM 和SCSS。 反向代理 假设我们实际访问地址为: https://plume.laosu.ml…...

机器人关节电机PWM
脉冲宽度调制(Pulse width modulation,PWM)技术。一种模拟控制方式 机器人关节电机的控制通常使用PWM(脉冲宽度调制)技术。PWM是一种用于控制电子设备的技术,通过控制高电平和低电平之间的时间比例,实现对电子设备的控制。在机器人关节电机中,PWM信号可以控制电机的…...

MPU6050详解(含源码)
前言:MPU6050是一款强大的六轴传感器,需要理解MPU6050首先得有IIC的基础,MPU6050 内部整合了 3 轴陀螺仪和 3 轴加速度传感器,并且含有一个第二 IIC 接口,可用于连接外部磁力传感器,内部有硬件算法支持. 1…...

Vue入门学习笔记:TodoList(三):实例中的数据、事件和方法
目录: Vue入门学习笔记:TodoList(一):HelloWorld Vue入门学习笔记:TodoList(二):挂载点、模板、实例 Vue入门学习笔记:TodoList(三)&a…...

怎么找到引发回流的JavaScript代码?
要找到引发回流的JavaScript代码,可以使用浏览器的开发者工具中的性能分析器。不同的浏览器有不同的名称和位置,例如Google Chrome的开发者工具中的性能分析器被称为Performance,Firefox的开发者工具中的性能分析器被称为Profiler。 以下是在…...

未来广告策划,转型还是淘汰?
在广告行业呆了十来年了,最近我越来越感觉到广告行业真的是一个需要与时俱进,并且应用场景非常广泛的一个专业。 而且由于这是一个需要创意能力的行业,所以对比于重复性容易被机器以及人工智能所代替的岗位行业来说,广告的可替代…...

【vscode远程开发】使用SSH远程连接服务器 「内网穿透」
文章目录 前言视频教程1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 转…...

七天从零实现Web框架Gee - 扩展
到这里前七天的任务已经完成,但我们可以对Gee框架进行一些扩展 补充HTTP请求方法 原作者只实现了 GET, POST 路由添加,其他的 PUT, DELETE 等标准 HTTP 方法未实现,实现方法也很简单,只需在gee.go中增加如下代码 // PUT define…...