WebGL实现透明物体(α混合)

news2024/11/19 15:34:08

目录

α混合 

如何实现α混合

1. 开启混合功能:

2. 指定混合函数 

混合函数 

gl.blendFunc()函数规范

可以指定给src_factor和dst_factor的常量 

混合后颜色的计算公式 

加法混合 

半透明的三角形(LookAtBlendedTriangles.js)

示例效果

示例代码 

半透明的三维物体(BlendedCube.js)

示例效果 

示例代码

透明与不透明物体共存

同时实现隐藏面消除和α混合的5个步骤

1.开启隐藏面消除功能。

2.绘制所有不透明的物体(α为1.0)。 

3.锁定用于进行隐藏面消除的深度缓冲区的写入操作,使之只读。 

4.绘制所有半透明的物体(α小于1.0),注意它们应当按照深度排序,然后从后向前绘制。 

5.释放深度缓冲区,使之可读可写。 

gl.depthMask()函数规范


α混合 

颜色中的α分量(即RGBA中的A)控制着颜色的透明度。如果一个物体颜色的α分量值为0.5,该物体就是半透明的,透过它可以看到该物体背后的其他物体。如果一个物体颜色的α分量值为0,那么它就是完全透明的,我们将完全看不到它。在本文的示例程序中,随着α分量的降低,整个绘图区域会逐渐成为白色,因为在默认情况下,α混合不仅影响绘制的物体,也会影响背景色,最后你看到的白色实际上是<canvas>后空白的网页。

如何实现α混合

需要遵循以下两个步骤来开启α混合。

1. 开启混合功能:

gl.enable(gl.BLEND)

2. 指定混合函数 

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

混合函数 

下面来研究gl.blendFunc()函数的作用。在进行α混合时,实际上WebGL用到了两个颜色,即源颜色(source color)和目标颜色(destination color),前者是“待混合进去”的颜色,后者是“待被混合进去的颜色”。比如说,我们先绘制了一个三角形,然后在这个三角形之上又绘制了一个三角形,那么在绘制后一个三角形(中与前一个三角形重叠区域的像素)的时候,就涉及混合操作,需要把后者的颜色“混入”前者中,后者的颜色就是源颜色,而前者的颜色就是目标颜色。

gl.blendFunc()函数规范

可以指定给src_factor和dst_factor的常量 

WebGL移除了OpenGL中的gl.CONSTANT_COLOR、gl.ONE_MINUS_CONSTANT_COLOR、gl.CONSTANT_ALPHA、gl.ONE_MINUS_CONSTANT_ALPHA

上表中,(Rs,Gs,Bs,As)和(Rd,Gd,Bd,Ad)表示源颜色和目标颜色的各个分量。

本文的示例程序都将使用

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

混合后颜色的计算公式 

这样,如果源颜色是半透明的绿色(0.0,1.0,0.0,0.4),目标颜色是普通(完全不透明)的黄色(1.0,1.0,0.0,1.0),那么src_factor即源颜色的α分量为0.4,而dst_factor则是(1- 0.4)=0.6,计算出混合后的颜色就是(0.6,1.0,0.0),如下图。 

加法混合 

你可以试试将src_factor和dst_factor参数指定为其他常量,比如,有一种常用的混合模式——加法混合 (additive blending),如下所示。加法混合会使被混合的区域更加明亮,通常被用来实现爆炸的光照效果,或者游戏中需要引起玩家注意的任务物品等。

gl.BlendFunc(gl.SRC_ALHPA, gl.ONE) 

半透明的三角形(LookAtBlendedTriangles.js

看一下示例程序LookAtBlendedTriangles,的运行效果如图中绘制了3个三角形并允许使用方向键来控制视点的位置。 本例将这3个三角形的α值从1.0改成0.4。下图显示了本例运行的效果。如你所见,三角形变成了半透明的,可以透过前面的三角形看到后面的三角形。使用方向键移动视点,可见半透明效果一直存在。、

示例效果

示例代码 

如下显示了LookAtBlendedTriangles.js的代码。我们开启了混合功能(第26行),指定了混合函数(第27行),并在initVertexBuffer()函数中修改三角形颜色的α分量值(第40~50行),gl.vertexAttribPointer()函数的size和stride参数也相应地作了修改。

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'uniform mat4 u_ViewMatrix;\n' +
  'uniform mat4 u_ProjMatrix;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_Position = u_ProjMatrix * u_ViewMatrix * a_Position;\n' +
  '  v_Color = a_Color;\n' +
  '}\n';
var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_FragColor = v_Color;\n' +
  '}\n';

function main() {
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
  var n = initVertexBuffers(gl);
  gl.clearColor(0, 0, 0, 1);
  gl.enable (gl.BLEND); // 开启混合功能
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); // 指定混合函数
  var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
  var u_ProjMatrix = gl.getUniformLocation(gl.program, 'u_ProjMatrix');
  var viewMatrix = new Matrix4();
  window.onkeydown = function(ev){ keydown(ev, gl, n, u_ViewMatrix, viewMatrix); };
  var projMatrix = new Matrix4();
  projMatrix.setOrtho(-1, 1, -1, 1, 0, 2);
  gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);
  // Draw
  draw(gl, n, u_ViewMatrix, viewMatrix);
}

function initVertexBuffers(gl) {
  var verticesColors = new Float32Array([
    // 顶点坐标和颜色数据(颜色透明度是 0.4)
    0.0,  0.5,  -0.4,  0.4,  1.0,  0.4,  0.4, // The back green one
   -0.5, -0.5,  -0.4,  0.4,  1.0,  0.4,  0.4,
    0.5, -0.5,  -0.4,  1.0,  0.4,  0.4,  0.4, 
    0.5,  0.4,  -0.2,  1.0,  0.4,  0.4,  0.4, // The middle yerrow one
   -0.5,  0.4,  -0.2,  1.0,  1.0,  0.4,  0.4,
    0.0, -0.6,  -0.2,  1.0,  1.0,  0.4,  0.4, 
    0.0,  0.5,   0.0,  0.4,  0.4,  1.0,  0.4,  // The front blue one 
   -0.5, -0.5,   0.0,  0.4,  0.4,  1.0,  0.4,
    0.5, -0.5,   0.0,  1.0,  0.4,  0.4,  0.4, 
  ]);
  var n = 9;
  var vertexColorbuffer = gl.createBuffer();  
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorbuffer);
  gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
  var FSIZE = verticesColors.BYTES_PER_ELEMENT;
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 7, 0);
  gl.enableVertexAttribArray(a_Position);
  var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  gl.vertexAttribPointer(a_Color, 4, gl.FLOAT, false, FSIZE * 7, FSIZE * 3);
  gl.enableVertexAttribArray(a_Color);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  return n;
}

function keydown(ev, gl, n, u_ViewMatrix, viewMatrix) {
    if(ev.keyCode == 39) { // 按下了向右箭头键
      g_EyeX += 0.01;
    } else 
    if (ev.keyCode == 37) { // 按下了向箭头键
      g_EyeX -= 0.01;
    } else return;
    draw(gl, n, u_ViewMatrix, viewMatrix);    
}

// 眼位
var g_EyeX = 0.20, g_EyeY = 0.25, g_EyeZ = 0.25;
function draw(gl, n, u_ViewMatrix, viewMatrix) {
  viewMatrix.setLookAt(g_EyeX, g_EyeY, g_EyeZ, 0, 0, 0, 0, 1, 0);
  gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.drawArrays(gl.TRIANGLES, 0, n);
}

半透明的三维物体(BlendedCube.js)

此刻,我们将利用α混合功能,在一个典型的三维物体——立方体上实现半透明效果。示例程序BlendedCube在WebGL 从0到1绘制一个立方体_山楂树の的博客-CSDN博客中的ColoredCube的基础上,向代码中加入了使用α混合的两个步骤,如下图所示。 

示例效果 

 运行程序,你会发现并没出现下图(右)中预期的效果,实际的效果如下图(左)所示,和WebGL 从0到1绘制一个立方体_山楂树の的博客-CSDN博客的ColoredCube没有什么区别。

这是因为,程序开启了隐藏面消除功能(第27行)。我们知道,α混合发生在绘制片元的过程,而当隐藏面消除功能开启时,被隐藏的片元不会被绘制,所以也就不会发生混合过程,更不会有半透明的效果。实际上,只需要注释掉这一行开启隐藏面消除的代码即可。 

示例代码

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'uniform mat4 u_MvpMatrix;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' +
  '  v_Color = a_Color;\n' +
  '}\n';
var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_FragColor = v_Color;\n' +
  '}\n';

function main() {
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
  var n = initVertexBuffers(gl);
  
  // 设置清除背景色并开启深度测试
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  // gl.enable(gl.DEPTH_TEST);
  // 开启α混合
  gl.enable (gl.BLEND);
  // 设置混合函数
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

  var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  var mvpMatrix = new Matrix4();
  mvpMatrix.setPerspective(30, 1, 1, 100);
  mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
  gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function initVertexBuffers(gl) {
  //    v6----- v5
  //   /|      /|
  //  v1------v0|
  //  | |     | |
  //  | |v7---|-|v4
  //  |/      |/
  //  v2------v3
  var vertices = new Float32Array([   // Vertex coordinates
     1.0, 1.0, 1.0,  -1.0, 1.0, 1.0,  -1.0,-1.0, 1.0,   1.0,-1.0, 1.0,    // v0-v1-v2-v3 front
     1.0, 1.0, 1.0,   1.0,-1.0, 1.0,   1.0,-1.0,-1.0,   1.0, 1.0,-1.0,    // v0-v3-v4-v5 right
     1.0, 1.0, 1.0,   1.0, 1.0,-1.0,  -1.0, 1.0,-1.0,  -1.0, 1.0, 1.0,    // v0-v5-v6-v1 up
    -1.0, 1.0, 1.0,  -1.0, 1.0,-1.0,  -1.0,-1.0,-1.0,  -1.0,-1.0, 1.0,    // v1-v6-v7-v2 left
    -1.0,-1.0,-1.0,   1.0,-1.0,-1.0,   1.0,-1.0, 1.0,  -1.0,-1.0, 1.0,    // v7-v4-v3-v2 down
     1.0,-1.0,-1.0,  -1.0,-1.0,-1.0,  -1.0, 1.0,-1.0,   1.0, 1.0,-1.0     // v4-v7-v6-v5 back
  ]);
  var colors = new Float32Array([     // Colors
      0.5, 0.5, 1.0, 0.4,  0.5, 0.5, 1.0, 0.4,  0.5, 0.5, 1.0, 0.4,  0.5, 0.5, 1.0, 0.4,  // v0-v1-v2-v3 front(blue)
      0.5, 1.0, 0.5, 0.4,  0.5, 1.0, 0.5, 0.4,  0.5, 1.0, 0.5, 0.4,  0.5, 1.0, 0.5, 0.4,  // v0-v3-v4-v5 right(green)
      1.0, 0.5, 0.5, 0.4,  1.0, 0.5, 0.5, 0.4,  1.0, 0.5, 0.5, 0.4,  1.0, 0.5, 0.5, 0.4,  // v0-v5-v6-v1 up(red)
      1.0, 1.0, 0.5, 0.4,  1.0, 1.0, 0.5, 0.4,  1.0, 1.0, 0.5, 0.4,  1.0, 1.0, 0.5, 0.4,  // v1-v6-v7-v2 left
      1.0, 1.0, 1.0, 0.4,  1.0, 1.0, 1.0, 0.4,  1.0, 1.0, 1.0, 0.4,  1.0, 1.0, 1.0, 0.4,  // v7-v4-v3-v2 down
      0.5, 1.0, 1.0, 0.4,  0.5, 1.0, 1.0, 0.4,  0.5, 1.0, 1.0, 0.4,  0.5, 1.0, 1.0, 0.4   // v4-v7-v6-v5 back
  ]);
  var indices = new Uint8Array([       // Indices of the vertices
     0, 1, 2,   0, 2, 3,    // front
     4, 5, 6,   4, 6, 7,    // right
     8, 9,10,   8,10,11,    // up
    12,13,14,  12,14,15,    // left
    16,17,18,  16,18,19,    // down
    20,21,22,  20,22,23     // back
  ]);
  var indexBuffer = gl.createBuffer();
  if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position')) return -1;
  if (!initArrayBuffer(gl, colors, 4, gl.FLOAT, 'a_Color')) return -1;
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  return indices.length;
}

function initArrayBuffer(gl, data, num, type, attribute) {
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  var a_attribute = gl.getAttribLocation(gl.program, attribute);
  gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
  gl.enableVertexAttribArray(a_attribute);
  return true;
}

透明与不透明物体共存

关闭隐藏面消除功能只是一个粗暴的解决方案,并不能满足实际的需求。在绘制三维场景时,场景中往往既有不透明的物体,也有半透明的物体。如果关闭隐藏面消除功能,那些不透明物体的前后关系就会乱套了。

同时实现隐藏面消除和α混合的5个步骤

实际上,通过某种机制,可以同时实现隐藏面消除和半透明效果,我们只需要:

1.开启隐藏面消除功能。

gl.enable(gl.DEPTH_TEST)

2.绘制所有不透明的物体(α为1.0)。 

3.锁定用于进行隐藏面消除的深度缓冲区的写入操作,使之只读。 

gl.depthMask(false)

4.绘制所有半透明的物体(α小于1.0),注意它们应当按照深度排序,然后从后向前绘制。 

5.释放深度缓冲区,使之可读可写。 

gl.depthMask(true) 

gl.depthMask()函数用来锁定和释放深度缓冲区,其规范如下所示: 

gl.depthMask()函数规范

我们知道,深度缓冲区存储了每个像素的z坐标值(归一化为0.0到1.0之间)。假设场景中有两个前后重叠的三角形A和B。首先,在绘制三角形A的时候,将它的每个片元的z值写入深度缓冲区,然后在绘制三角形B的时候,将B中与A重叠的片元和深度缓冲区中对应像素的z值作比较:如果深度缓冲区中的z值小,就说明三角形A在前面,那么B的这个片元就被舍弃了,不会写入颜色缓冲区;如果深度缓冲区中的z值大,就说明三角形B在前面,就把B的这个片元写入颜色缓冲区中,将之前A的颜色覆盖掉。这样,当绘制完成时,颜色缓冲区中的所有像素都是最前面的片元,而且每个像素的z值都存储在深度缓冲区中。这就是隐藏面消除的原理。注意,所有这些操作都是在片元层面上进行的,所以如果两个面相交,也可以正常显示。 

当我们按照上述步骤同时绘制透明和不透明物体时,在第1步和第2步结束后,所有不透明的物体都正确地绘制在了颜色缓冲区中,深度缓冲区记录了每个像素(最前面的片元)的深度。然后进行第3、4和5步,绘制所有半透明的物体。由于深度缓冲区的存在,不透明物体后面的物体,即使是半透明的也不会显示。这是因为深度缓冲区的写操作被锁定了,所以在绘制不透明物体前面的透明物体时,也不会更新深度缓冲区。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1046591.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用Python和Pandas处理网页表格数据

在我们的日常工作和生活中,经常会遇到需要处理大量数据的情况,而网页表格数据则是其中常见的一种形式。如果我们能够灵活地使用Python和Pandas这两个强大的工具,就能够快速、高效地对这些数据进行处理和分析。 首先,我们需要了解…

游戏服务商Latis Global参展2023 ChinaJoy B2B

第20届ChinaJoy于2023年7月在上海举行了为期四天的博览会,参展观众达到了33.8万人次。ChinaJoy是全球最具知名度与影响力的年度盛会之一,涵盖了包括游戏、动漫、互联网影视、电子竞技、潮流玩具、智能娱乐在内的多个数字娱乐领域。ChinaJoy不仅仅代表了数字娱乐领域的最新风向,…

搞定零售出海的底层挑战,泡泡玛特的经验是什么?

出品 | CSDN 云计算 从 2010 年成立,2016 年发布 Molly IP 的盲盒产品到现在,泡泡玛特已经成为在全球拥有员工 4000 多人的知名潮流玩具品牌。就像在中国市场被喜爱的迪士尼、日漫等多个 IP 一样,泡泡玛特从 2022 年开始也带着众多 IP 走出中…

如何正确监测蓄电池健康?狠狠学到!

蓄电池在现代生活和工业中发挥着关键作用,它们为无数设备和系统提供了必要的电力支持。然而,蓄电池的性能和可靠性对许多应用至关重要。监控蓄电池状态和性能变得越来越重要,以确保它们在需要时始终可用。 为此,蓄电池监控系统应运…

从零开始之了解电机及其控制(8)clarke和park变换

为了最大化无刷电机产生的扭矩,我们希望感应磁场与转子磁场正交并引导转子磁场,无刷电机定子产生的磁场矢量将与电流矢量逆时针旋转 90 度 但现在我们要稍微改变这个y电路的外观,我们不是将相位表示为电阻器,它会产生垂直于电流方…

家电行业 EDI:Miele EDI 需求分析

Miele是一家创立于1899年的德国公司,以其卓越的工程技术和不懈的创新精神而闻名于世。作为全球领先的家电制造商,Miele的经营范围覆盖了厨房、洗衣和清洁领域,致力于提供高品质、可持续和智能化的家电产品。公司的使命是为全球消费者创造更美…

TSM动作识别模型【详解】

文章目录 本文使用的是somethingv2数据集,解压后是如下形式; 由于该压缩数据进行了分卷操作,需要合并后才能进行解压。首先我们将下面4个json文件剪贴到其他文件夹,只保留00-19的文件,然后在该文件夹下打开cmd&#xf…

Unity vscode 官方debug

把这个先unlock 再升级到最新的 然后重新生成 project files 打开 launch.json {"version": "0.2.0","configurations": [{"name": "Attach to Unity","type": "vstuc","request": "a…

创造您梦寐以求的家居设计——Live Home 3D Pro for Mac

您是否曾经想象过在舒适的家中展现自己独特的风格?现在,您可以通过Live Home 3D Pro for Mac来实现您的家居设计梦想!这款强大的3D家居设计软件将带给您无限的创作可能性。 Live Home 3D Pro for Mac是一款专业级的家居设计软件,…

Java初始化大量数据到Neo4j中(二)

接Java初始化大量数据到Neo4j中(一)继续探索,之前用create命令导入大量数据发现太过耗时,查阅资料说大量数据初始化到Neo4j需要使用neo4j-admin import 业务数据说明可以参加Java初始化大量数据到Neo4j中(一),这里主要是将处理好的节点数据和…

opencv英文识别tesseract-orc安装

文章目录 一、安装并保存所在路径二、配置环境变量1、打开高级设置2、配置环境变量三、修改tesseract.py文件中的路径,否则运行报错1、进入python所在的文件夹,找到Lib,site-packages2、搜索pytesseract3、打开py文件修改路径一、安装并保存所在路径 特别注意路径名中不能有…

离线环境harbor 搭建及使用

一 摘要 本文主要介绍harbor 的安装及使用。 二 环境信息及部署图 2.1 环境信息 名称版本备注操作系统centos7.9容器docker 23.0.1harbor2.7代理nginx待补充 2.2 架构图 说明: 1.harbor 核心服务里有个nginx ,也可以用该nginx 做代理 2.proxy-ngin…

推荐几款优秀的项目报表软件

项目报表在项目工作中扮演着重要的角色,它是领导和客户了解项目进况的直接途径。有需求就会有市场,为解决传统报表制作复杂困难的问题,专业报表工具应运而生。 一款好用的项目报表软件可以帮助项目团队快速产出项目报表,实现数据…

深度学习实战54-基于ChatGLM2大模型构建智能自助用药问答系统与药物智能管理实战的应用

大家好,我是微学AI,今天给大家介绍一下深度学习实战54-基于ChatGLM2大模型构建智能自助用药问答系统与药物智能管理实战的应用。 随着人工智能技术的发展,我们的生活在许多方面都得到了改善。医疗领域也不例外。本文将介绍如何利用大模型&…

【Python自动化测试】mock模块基本使用介绍

mock简介 py3已将mock集成到unittest库中为的就是更好的进行单元测试简单理解,模拟接口返回参数通俗易懂,直接修改接口返回参数的值官方文档:unittest.mock --- 模拟对象库 — Python 3.11.4 文档 mock作用 解决依赖问题,达到解…

软件测试/测试开发 | AI大模型应用开发实训营来啦~ 大模型学习资料免费领

因为 AIGC 持续火热,越来越多的企业都需要借助大模型来为自己的业务赋能,也就是产出适合自己公司业务情况的智能化产品,这是目前程序员必须要面对的难题和挑战。如果要在企业内部落地相关引用,就需要员工具备响应的大模型应用开发…

LCR 101. 分割等和子集——力扣——背包问题、动态规矩

问题描述 代码展示 class Solution:def canPartition(self, nums: List[int]) -> bool:if len(nums) < 1:return Falsetotal_sum sum(nums)if total_sum % 2 ! 0: # 总和为奇数&#xff0c;无法分成两个相等的子集return Falsetarget_sum total_sum // 2dp [[False]…

el-tooltip内容换行显示

效果图&#xff1a; html: <div class"rules-tooltip flex-center"><el-tooltip class"item" effect"dark" placement"bottom-start"><div slot"content" v-html"tipsContent"></div>&l…

工具篇 | WSL使用入门教程以及基于WSL和natApp内网穿透实践 - 对比VMWare

介绍 在开发工具中&#xff0c;Windows Subsystem for Linux (WSL) 和 VMWare 它们都可以实现了在 Windows 上运行 Linux系统。 文章概览 WSL Vs VMWare 我们将简单比对 WSL 和 VMWare&#xff0c;在性能、资源消耗等方面的差异&#xff0c;以协助您做出更加明确的选择。 …

专业级操作,如何快速批量虚化多个视频的背景边框

如果你是一名视频编辑爱好者&#xff0c;或者是一名需要处理大量视频素材的专业人士&#xff0c;那么你可能会对如何快速处理和虚化视频的背景边框感到困惑。这里&#xff0c;我们就为你提供一种方法&#xff0c;使用固乔剪辑助手工具&#xff0c;你可以轻松实现批量虚化多个视…