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

使用 Three.js 后处理的粗略铅笔画效果

本文使用Three.js的后处理创建粗略的铅笔画效果。我们将完成创建自定义后处理渲染通道、在 WebGL中实现边缘检测、将法线缓冲区重新渲染到渲染目标以及使用生成和导入的纹理调整最终结果的步骤。翻译自Codrops,有改动。

Three.js 中的后处理

Three.js中的后处理是一种在绘制场景后将效果应用于渲染场景的方法。除了Three.js提供的所有开箱即用的后处理效果外,还可以通过创建自定义渲染通道来添加我们自己的滤镜。

自定义渲染过程本质上是一个函数,它接收场景图像并返回一个新图像,并应用所需的效果。我们可以将这些渲染通道想象成Photoshop中的图层效果————每个渲染通道都基于之前的效果输出应用新的滤镜。生成的图像是所有不同效果(滤镜)的组合。

在 Three.js 中启用后处理

要向我们的场景添加后处理效果,我们需要设置EffectComposer来进行场景渲染。这个EffectComposer将后处理效果按传递顺序叠加在一起。如果我们想让我们渲染的场景传递给下一个效果,我们需要先利用RenderPass创建一个后处理通道。

然后,在启动渲染循环的tick函数中,我们调用composer.render()来代替renderer.render(scene, camera)

const renderer = new THREE.WebGLRenderer()const composer = new EffectComposer(renderer)
const renderPass = new RenderPass(scene, camera)composer.addPass(renderPass)function tick() {requestAnimationFrame(tick)composer.render()
}tick()

有两种创建自定义后处理效果的方法:

1.创建自定义着色器并将其传递给ShaderPass实例,或者
2.通过扩展Pass类来创建自定义渲染通道。

因为我们希望我们的后处理效果获得比uniform和attribute更多的信息,所以我们将创建一个自定义渲染通道。

创建自定义渲染通道

一个自定义通道继承自Pass类,并具有三个方法:setSizerenderdispose,我们将主要关注render方法。

首先,我们扩展Pass类来创建自己的PencilLinesPass类,然后再实现我们自己的渲染逻辑。

import { Pass, FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass'
import * as THREE from 'three'export class PencilLinesPass extends Pass {constructor() {super()}render(renderer: THREE.WebGLRenderer,: THREE.WebGLRenderTarget,readBuffer: THREE.WebGLRenderTarget) {if (this.renderToScreen) {renderer.setRenderTarget(null)} else {renderer.setRenderTarget(writeBuffer)if (this.clear) renderer.clear()}}
}

从上面代码中可以看出该render方法接受一个WebGLRenderer对象和两个WebGLRenderTarget对象(一个用于写入缓冲区,另一个用于读取缓冲区)。在Three.js中,渲染目标一般是我们可以渲染到场景的纹理,它们用于在通道之间发送数据。readBuffer从先前的渲染通道接收数据,在我们的例子中是默认的RenderPass;writeBuffer则是将数据发送到下一个渲染通道。

renderToScreen为true的时候,则意味着我们要将缓冲区发送到屏幕而不是渲染目标。渲染器的渲染目标设置为null的时候,默认就是为屏幕画布。

在这一点上,我们实际上并没有渲染任何东西,甚至没有通过readBuffer传入数据。为了渲染场景事物,我们需要创建一个FullscreenQuad和一个负责渲染的着色器材质,然后将着色器材质渲染到FullscreenQuad

为了测试一切设置是否正确,我们可以使用threejs内置的CopyShader来显示我们放入其中的任何图像。

import { Pass, FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass'
import { CopyShader } from 'three/examples/jsm/shaders/CopyShader'
import * as THREE from 'three'export class PencilLinesPass extends Pass {fsQuad: FullScreenQuadmaterial: THREE.ShaderMaterialconstructor() {super()this.material = new THREE.ShaderMaterial(CopyShader)this.fsQuad = new FullScreenQuad(this.material)}dispose() {this.material.dispose()this.fsQuad.dispose()}render(renderer: THREE.WebGLRenderer,writeBuffer: THREE.WebGLRenderTarget,readBuffer: THREE.WebGLRenderTarget) {this.material.uniforms['tDiffuse'].value = readBuffer.textureif (this.renderToScreen) {renderer.setRenderTarget(null)this.fsQuad.render(renderer)} else {renderer.setRenderTarget(writeBuffer)if (this.clear) renderer.clear()this.fsQuad.render(renderer)}}
}

注意:我们将uniform变量tDiffuse传递给着色器材质。CopyShader已经内置了这个uniform,它代表要在屏幕上渲染显示的图像。如果你正在编写自己的ShaderPass,这个uniform将自动传递到你的着色器中。

剩下的就是通过将自定义渲染通道添加到EffectComposer来将自定义渲染通道连接到场景中,而且注意要在添加完RenderPass之后。

const renderPass = new RenderPass(scene, camera)
const pencilLinesPass = new PencilLinesPass()composer.addPass(renderPass)
composer.addPass(pencilLinesPass)

查看 Codesandbox 示例


具有自定义渲染通道和 CopyShader 的场景

用于创建轮廓的 Sobel 算子

我们需要能够告诉计算机根据我们的输入图像(即场景图像)检测边缘线条,我们将使用的这种边缘检测称为 Sobel 算子。

Sobel 算子通过查看图像一小部分的梯度来进行边缘检测————本质上是检查从一个值到另一个值的过渡有多尖锐。图像被分解成更小的“内核”,比如说是 3px x 3px 的正方形,其中中心像素是当前正在处理的像素。下图显示了它的样子:中心的红色方块代表当前正在评估的像素,其余方块是它的邻近像素。


3px x 3px 内核

然后通过获取像素值(亮度)并将其乘以基于其相对于被评估像素的位置的权重来计算每个邻近像素的加权值。这是通过权重在水平和垂直方向上偏置梯度来完成的。取两个值的平均值,如果它超过某个阈值,我们认为该像素表示边缘。


Sobel 算子的水平和垂直梯度

Three.js 已经为我们提供了SobelOperatorShader中的代码,我们可以将这段代码复制到我们的着色器材质中。

实现 Sobel 算子

我们现在需要添加我们自己的ShaderMaterial来代替CopyShader,以便我们可以控制顶点和片段着色器,以及发送给那些着色器的uniform。

// PencilLinesMaterial.ts
export class PencilLinesMaterial extends THREE.ShaderMaterial {constructor() {super({uniforms: {tDiffuse: { value: null },// 我们稍后会在这里传递画布大小uResolution: {value: new THREE.Vector2(1, 1)}},fragmentShader, vertexShader})}
}

然后我们需要在场景中使用我们的新着色器材质。

// PencilLinesPass.ts
export class PencilLinesPass extends Pass {fsQuad: FullScreenQuadmaterial: PencilLinesMaterialconstructor({ width, height }: { width: number; height: number }) {super()// 将材质更改为我们新的PencilLinesMaterialthis.material = new PencilLinesMaterial() this.fsQuad = new FullScreenQuad(this.material)// 将 uResolution 设置为当前画布的宽度和高度this.material.uniforms.uResolution.value = new THREE.Vector2(width, height)}
}

接下来,我们可以编写顶点和片段着色器。

除了设置gl_Position并将uv属性传递给片段着色器之外,顶点着色器并没有做其他事情。因为我们将图像渲染到FullscreenQuad,所以uv信息对应于任何给定片段在屏幕上的位置。

// vertex shader
varying vec2 vUv;void main() {vUv = uv;gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

片元着色器要复杂一些,所以我们逐行进行分解。首先,我们要使用Three.js已经提供的实现算法来实现Sobel算子。唯一的区别是我们想要控制我们如何计算每个像素的值,因为我们也将引入法线缓冲区的线检测。

float combinedSobelValue() {// 内核定义(在 glsl 中,矩阵按列优先顺序填充)const mat3 Gx = mat3(-1, -2, -1, 0, 0, 0, 1, 2, 1);// x方向内核const mat3 Gy = mat3(-1, 0, 1, -2, 0, 2, -1, 0, 1);// y方向内核// 获取片段的 3x3 邻域// 第一列float tx0y0 = getValue(-1, -1);float tx0y1 = getValue(-1, 0);float tx0y2 = getValue(-1, 1);// 第二列float tx1y0 = getValue(0, -1);float tx1y1 = getValue(0, 0);float tx1y2 = getValue(0, 1);// 第三列float tx2y0 = getValue(1, -1);float tx2y1 = getValue(1, 0);float tx2y2 = getValue(1, 1);// x方向的梯度值float valueGx = Gx[0][0] * tx0y0 + Gx[1][0] * tx1y0 + Gx[2][0] * tx2y0 +Gx[0][1] * tx0y1 + Gx[1][1] * tx1y1 + Gx[2][1] * tx2y1 +Gx[0][2] * tx0y2 + Gx[1][2] * tx1y2 + Gx[2][2] * tx2y2;// y方向的梯度值float valueGy = Gy[0][0] * tx0y0 + Gy[1][0] * tx1y0 + Gy[2][0] * tx2y0 +Gy[0][1] * tx0y1 + Gy[1][1] * tx1y1 + Gy[2][1] * tx2y1 +Gy[0][2] * tx0y2 + Gy[1][2] * tx1y2 + Gy[2][2] * tx2y2;// 总梯度的大小float G = (valueGx * valueGx) + (valueGy * valueGy);return clamp(G, 0.0, 1.0);
}

我们将当前像素的偏移量传递给getValue函数,在获取邻域像素的值。目前,我们仅需要评估漫反射缓冲区的值,我们将在下一步中添加法线缓冲区。

float valueAtPoint(sampler2D image, vec2 coord, vec2 texel, vec2 point) {vec3 luma = vec3(0.299, 0.587, 0.114);return dot(texture2D(image, coord + texel * point).xyz, luma);
}float diffuseValue(int x, int y) {return valueAtPoint(tDiffuse, vUv, vec2(1.0 / uResolution.x, 1.0 / uResolution.y), vec2(x, y)) * 0.6;
}float getValue(int x, int y) {return diffuseValue(x, y);
}

valueAtPoint函数可以输入任何纹理(漫反射或法线)并返回指定点的灰度值。luma向量用于计算颜色的亮度,从而将rgb颜色转换为灰度值。这个实现来自glsl-luma。

因为getValue函数只考虑漫反射缓冲区,这意味着场景中的任何边缘都将被检测到,包括由投射的阴影创建的边缘。这也意味着例如物体的轮廓,如果它们与周围环境(投射的阴影)融合得太好,可能会被忽略。为了捕获那些缺失的边缘,我们接下来将从法线缓冲区添加边缘检测。

最后,我们在主函数中调用 Sobel 算子,如下所示:

void main() {float sobelValue = combinedSobelValue();sobelValue = smoothstep(0.01, 0.03, sobelValue);vec4 lineColor = vec4(0.32, 0.12, 0.2, 1.0);if (sobelValue > 0.1) {gl_FragColor = lineColor;} else {gl_FragColor = vec4(1.0);}
}

查看 Codesandbox 示例

创建一个法线缓冲区渲染

为了获得合适的轮廓,Sobel算子通常应用于场景的法线和深度缓冲区,因此会捕获对象的轮廓,但不会捕获对象内的线条。Omar Shehata 在他的How to render outlines in WebGL教程中描述了这种方法。出于只是实现粗略铅笔效果的目的,我们不需要完整的边缘检测,但我们确实希望使用法线来获得更完整的边缘。

由于法线是表示对象表面每个点方向的向量,因此通常用颜色表示以获取包含场景中所有法线数据的图像。这张图被称为“法线缓冲区”。

为了创建一个法线缓冲区,首先我们需要在PencilLinesPass构造函数中创建一个新的渲染目标。我们还需要在类上创建一个MeshNormalMaterial,因为我们将在渲染法线缓冲区时使用它来覆盖场景的默认材质。

const normalBuffer = new THREE.WebGLRenderTarget(width, height)normalBuffer.texture.format = THREE.RGBAFormat
normalBuffer.texture.type = THREE.HalfFloatType
normalBuffer.texture.minFilter = THREE.NearestFilter
normalBuffer.texture.magFilter = THREE.NearestFilter
normalBuffer.texture.generateMipmaps = false
normalBuffer.stencilBuffer = false
this.normalBuffer = normalBufferthis.normalMaterial = new THREE.MeshNormalMaterial()

为了渲染通道内的场景,我们还需要通过渲染通道的构造函数来传入scene和camera。

// PencilLinesPass.ts 构造函数
constructor({ ..., scene, camera}: { ...; scene: THREE.Scene; camera: THREE.Camera }) {super()this.scene = scenethis.camera = camera...
}

在渲染通道的render方法中,我们想要使用覆盖默认材质的法线材质重新渲染场景。我们将renderTarget设置为normalBuffer,并像往常一样使用WebGLRenderer渲染场景。唯一的区别是,渲染器不是使用场景的默认材质渲染到屏幕,而是使用法线材质渲染到我们的渲染目标(此处即为我们的normalBuffer)。然后我们将normalBuffer.texture传递给着色器材质。overrideMaterial参数表示强制使用定义的材质渲染场景中的所有内容。

renderer.setRenderTarget(this.normalBuffer)
const overrideMaterialValue = this.scene.overrideMaterialthis.scene.overrideMaterial = this.normalMaterial
renderer.render(this.scene, this.camera)
this.scene.overrideMaterial = overrideMaterialValuethis.material.uniforms.uNormals.value = this.normalBuffer.texture
this.material.uniforms.tDiffuse.value = readBuffer.texture

如果此时我们利用texture2D(uNormals,vUv);将法线缓冲区的值赋给gl_FragColor,渲染结果将是下图所示:

当前场景的法线缓冲区

在自定义材质的片段着色器中,我们修改getValue函数,让它包含漫反射缓冲区和法线缓冲区的 Sobel 算子。如果我们在这里只计算法线缓冲区,会发现平面阴影的边缘就没有了,因为平面法线是没有过渡的。

float normalValue(int x, int y) {return valueAtPoint(uNormals, vUv, vec2(1.0 / uResolution.x, 1.0 / uResolution.y), vec2(x, y)) * 0.3;
}float getValue(int x, int y) {return diffuseValue(x, y) + normalValue(x, y);
}

查看 Codesandbox 示例

为着色和波浪线添加生成的纹理噪声

有两种方法可以将噪声带入后处理效果:

  1. 通过在着色器中由程序生成噪声,或者
  2. 通过使用带有噪声的图像并将其应用为纹理。

两者都提供了不同级别的灵活性和控制。对于噪声函数,我们使用Inigo Quilez的梯度噪声实现算法,因为它在应用于“着色”效果时提供了很好的噪声均匀性。

这个噪声函数是在获取Sobel算子的值时调用的,并专门作用于法线值,所以片段着色器中getValue的函数变化如下:

float getValue(int x, int y) {float noiseValue = noise(gl_FragCoord.xy);noiseValue = noiseValue * 2.0 - 1.0;noiseValue *= 10.0;return diffuseValue(x, y) + normalValue(x, y) * noiseValue;
}

这样得出来的结果是在法向量值发生变化时,对象曲线上形成带纹理的铅笔线和点画效果。请注意,平面对象(如Plane)不会产生这些效果,因为它们的法线值没有任何变化。

此效果的下一步也是最后一步是为线条添加扭曲。为此,我们使用了在Photoshop中使用渲染云效果创建的纹理文件。


在 Photoshop 中创建的生成的云纹理

云纹理通过一个uniform变量传递给着色器,与漫反射和法线缓冲区的方式相同。一旦着色器可以访问纹理,我们就可以对每个片段的纹理进行采样,并使用它来偏移我们在缓冲区中读取的位置。本质上,我们通过扭曲我们正在读取的图像来获得波浪线效果。因为纹理的噪点是平滑的,线条不会出现锯齿状和不规则的情况。

float normalValue(int x, int y) {float cutoff = 50.0;float offset = 0.5 / cutoff;float noiseValue = clamp(texture(uTexture, vUv).r, 0.0, cutoff) / cutoff - offset;return valueAtPoint(uNormals, vUv + noiseValue, vec2(1.0 / uResolution.x, 1.0 / uResolution.y), vec2(x, y)) * 0.3;
}

查看 Codesandbox 示例

结论

有许多技术可以在3D中创建手绘或素描效果。我们可以通过基于噪声纹理调制被认为是边缘的阈值来调整线条粗细。我们还可以将Sobel算子应用于深度缓冲区,完全忽略漫反射缓冲区,以获得没有轮廓阴影的轮廓对象。我们可以根据场景中的照明信息而不是基于对象的法线来添加生成的噪声。接下来我会将这种效果应用到cesium和mapbox上。

相关文章:

使用 Three.js 后处理的粗略铅笔画效果

本文使用Three.js的后处理创建粗略的铅笔画效果。我们将完成创建自定义后处理渲染通道、在 WebGL中实现边缘检测、将法线缓冲区重新渲染到渲染目标以及使用生成和导入的纹理调整最终结果的步骤。翻译自Codrops,有改动。 Three.js 中的后处理 Three.js中的后处理是一…...

推荐一些不常见的搜索引擎

5.雅虎网来自 Yahoo.com 的屏幕截图,2023 年 2 月截至 2022 年 1 月,Yahoo.com(Verizon Media)的搜索市场份额为 11.2%。雅虎的优势在于多元化,除搜索外还提供电子邮件、新闻、金融等服务。二十多年来,雅虎…...

RabbitMQ工作模式

目录1.Work queues工作队列模式1.1 模式说明1.2 代码1.3 测试1.4 小结2.订阅模式类型3.Publish/Subscribe发布与订阅模式3.1 模式说明3.2 代码3.3 测试3.4 小结4.Routing路由模式4.1 模式说明4.2 代码4.3 测试4.4 小结5.Topics通配符模式5.1 模式说明5.2 代码5.3 测试5.4 小结6…...

机器学习在预测脊髓型颈椎病中的应用:一项28名参与者的事后初步研究

机器学习在预测脊髓型颈椎病中的应用:一项28名参与者的事后初步研究 Machine Learning for the Prediction of Cervical Spondylotic Myelopathy: A Post Hoc Pilot Study of 28 Participants 简单说:训练了两个模型:1)预测脊髓型颈椎病诊断&#xff0…...

【智能计算数学】微积分

高数问题解决流程引例:回归回归引例:分类分类线性可分FLD线性不可分智能计算讨论范围下降法为什么要用下降法?- 解析解很难写出公式或很复杂难计算有哪些常用的下降法?- 梯度下降&高斯-牛顿法梯度下降(Gradient De…...

win10+RTX4070ti+libtorch部署

环境cuda 11.7、cudnn8.6.0、libtorch1.13.1cu117 注意: 1)libtorch官网进不去的可直接下载 Release version https://download.pytorch.org/libtorch/cu117/libtorch-win-shared-with-deps-1.13.1%2Bcu117.zip Debug version https://download.pytorch.…...

【Python百日进阶-Web开发-Vue3】Day518 - Vue+ts后台项目5:用户列表

文章目录 一、获取用户列表的数据1.1 定义用户列表和角色列表的接口src/request/api.ts1.2 获取用户列表数据src/views/UserView.vue二、定义用户列表数据类型2.1 src/type/user.ts三、展示用户列表内容3.1 element-plus中的Select 选择器3.2 element-plus中的表格插槽3.3 展示…...

Linux内核转储---kdump原理梳理

文章目录Kexec和Kdump设计的区别kexeckdumpKdump的执行流程kexec的实现用户空间kexec内核空间vmcoreKdump的实现可以分为两部分:内核和用户工具。内核提供机制,用户工具在这些机制上实现各种转储策略,内核机制对用户工具的接口是一个系统调用…...

【C++】从0到1入门C++编程学习笔记 - 实战篇:演讲比赛流程管理系统

文章目录一、演讲比赛程序需求1.1 比赛规则1.2 程序功能1.3 程序效果图:二、项目创建2.1 创建项目2.2 添加文件三、创建管理类3.1创建文件3.2 头文件实现3.3 源文件实现四、菜单功能4.1 添加成员函数4.2 菜单功能实现4.3 测试菜单功能五、退出功能5.1 提供功能接口5…...

04 OpenCV位平面分解

1 基本概念 位平面分解的核心思想是将图像的每一个像素分解为多个二进制位,分别存储在不同的位平面上。例如,如果一个图像是8位深度的,则可以分解为8个位平面,每个位平面上存储一个二进制位。 位平面分解在图像压缩中有着重要的…...

Onvif协议如何判断摄像机支持 —— 筑梦之路

有人就问什么是Onvif协议呢? 全称为:Open Network Video Interface Forum.缩写成Onvif。 翻译过来是:开放型网络视频接口论坛,目的是确保不同安防厂商的视频产品能够具有互通性,这样对整体安防行业才是良性发展。 现…...

情人节new一个对象给你

今天情人节,有没对象的吗?假设你不知道new怎么用,每个人都有两种身份,一种没对象的人,这个时候new一个对象给你,一种是有对象的人,这个delete对象。等你学完这个new和delete知识点,无…...

linux篇【15】:应用层-网络https协议

目录 一.HTTPS介绍 1.HTTPS 定义 2.HTTP与HTTPS (1)端口不同,是两套服务 (2)HTTP效率更高,HTTPS更安全 3.加密,解密,密钥 概念 4.为什么要加密? 5.常见的加密方式…...

索引-性能分析-explain

explain 执行计划 explain 执行计划各字段含义 1)id 就是代表 sql 的执行顺序或者表的执行顺序;id相同从上往下执行,id不同,id值越大越先执行;(注:有子查询时就会出现sql执行顺序)…...

mbedtls加密组件使用示例

1 mbedtls aes组件的使用 1.1 AES ECB加解密接口使用 int main(int argc, char *argv[]) {char key[256];char *inbuf calloc(1, 257);char *outbuf calloc(1, 257);char *buf calloc(1,257);char *tmp_outbuf outbuf;char *tmp_buf buf;mbedtls_aes_context aes_ctx;mb…...

如何量测太阳光模拟器的光谱致合度?

太阳模拟器是根据国际法规JIS、IEC60904、美国材料试验协会开发设计的AAA级太阳模拟器。对于100毫米100毫米和200毫米200毫米的光斑尺寸,光斑强度的输出功率范围可以从0.1到1太阳光强度。此外,还提供了灵活的出光方向,以满足用户的研究需求&a…...

网络安全领域中CISP证书八大类都有什么

CISP​注册信息安全专业人员 注册信息安全专业人员(Certified Information Security Professional),是经中国信息安全产品测评认证中心实施的国家认证,对信息安全人员执业资质的认可。该证书是面向信息安全企业、信息安全咨询服务…...

17- 梯度提升回归树GBRT (集成算法) (算法)

梯度提升回归树: 梯度提升回归树是区别于随机森林的另一种集成方法,它的特点在于纠正与加强,通过合并多个决策树来构建一个更为强大的模型。该模型即可以用于分类问题,也可以用于回归问题中。在该模型中,有三个重要参数分别为 n_…...

05 OpenCV色彩空间处理

色彩空间(Color Space)是一种用于描述颜色的数学模型,它将颜色表示为多维向量或坐标,通常由三个或四个独立的分量来表示。不同的色彩空间在颜色的表示方式、可表达颜色的范围、计算速度和应用场景等方面存在差异,不同的…...

【CS224图机器学习】task1 图机器学习导论

前言:本期学习是由datawhale(公众号)组织,由子豪兄讲解的202302期CS224图机器学习的学习笔记。本次学习主要针对图机器学习导论做学习总结。1.什么是图机器学习?通过图这种数据结构,对跨模态数据进行整理。…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

消息队列系统设计与实践全解析

文章目录 &#x1f680; 消息队列系统设计与实践全解析&#x1f50d; 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡&#x1f4a1; 权衡决策框架 1.3 运维复杂度评估&#x1f527; 运维成本降低策略 &#x1f3d7;️ 二、典型架构设计2.1 分布式事务最终一致…...

基于鸿蒙(HarmonyOS5)的打车小程序

1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...