二、WebGPU阶段间变量(inter-stage variables)

news2024/11/14 15:38:46

二、WebGPU阶段间变量(inter-stage variables)

在上一篇文章中,我们介绍了一些关于WebGPU的基础知识。在本文中,我们将介绍阶段变量(inter-stage variables)的基础知识。

阶段变量在顶点着色器和片段着色器之间起作用。当顶点着色器输出3个位置时,三角形将栅格化。顶点着色器可以在每个位置输出额外的值,默认情况下,这些值将在3个点之间进行插值。让我们举个小例子。我们将从上一篇文章中的三角形着色器开始。

我们要做的就是改变着色器。

  const module = device.createShaderModule({
    label: 'our hardcoded rgb triangle shaders',
    code: `
      struct OurVertexShaderOutput {
        @builtin(position) position: vec4f,
        @location(0) color: vec4f,
      };
 
      @vertex fn vs(
        @builtin(vertex_index) vertexIndex : u32
      ) -> OurVertexShaderOutput {
        let pos = array(
          vec2f( 0.0,  0.5),  // top center
          vec2f(-0.5, -0.5),  // bottom left
          vec2f( 0.5, -0.5)   // bottom right
        );
        var color = array<vec4f, 3>(
          vec4f(1, 0, 0, 1), // red
          vec4f(0, 1, 0, 1), // green
          vec4f(0, 0, 1, 1), // blue
        );
 
        var vsOutput: OurVertexShaderOutput;
        vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);
        vsOutput.color = color[vertexIndex];
        return vsOutput;
      }
 
      @fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {
        return fsInput.color;
      }
    `,
  });

首先,我们声明一个结构体。这是一个在顶点着色器和片段着色器之间协调阶段间变量的简单方法。

      struct OurVertexShaderOutput {
        @builtin(position) position: vec4f,
        @location(0) color: vec4f,
      };

然后我们声明顶点着色器来返回这种类型的结构

      @vertex fn vs(
        @builtin(vertex_index) vertexIndex : u32
      ) -> OurVertexShaderOutput {

我们创建一个包含3种颜色的数组。

  var color = array<vec4f, 3>(
          vec4f(1, 0, 0, 1), // red
          vec4f(0, 1, 0, 1), // green
          vec4f(0, 0, 1, 1), // blue
        );

然后不是返回一个vec4f来获取位置,而是我们声明一个结构的实例,填充它,然后返回它

      var vsOutput: OurVertexShaderOutput;
        vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);
        vsOutput.color = color[vertexIndex];
        return vsOutput;

在片段着色器中,我们声明它将这些结构之一作为函数的参数

   @fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {
        return fsInput.color;
      }

然后返回颜色。
如果我们运行它,我们会看到,每次GPU调用我们的片段着色器时,它都会传递在所有3个点之间插值的颜色。

以下为当前代码及运行结果:

HTML:

<!--
 * @Description: 
 * @Author: tianyw
 * @Date: 2022-11-11 12:50:23
 * @LastEditTime: 2023-09-17 16:33:32
 * @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>001hello-triangle</title>
    <style>
        html,
        body {
            margin: 0;
            width: 100%;
            height: 100%;
            background: #000;
            color: #fff;
            display: flex;
            text-align: center;
            flex-direction: column;
            justify-content: center;
        }

        div,
        canvas {
            height: 100%;
            width: 100%;
        }
    </style>
</head>

<body>
    <div id="003color-triangle">
        <canvas id="gpucanvas"></canvas>
    </div>
    <script type="module" src="./003color-triangle.ts"></script>

</body>

</html>

TS:

/*
 * @Description:
 * @Author: tianyw
 * @Date: 2023-04-08 20:03:35
 * @LastEditTime: 2023-09-17 21:06:44
 * @LastEditors: tianyw
 */
export type SampleInit = (params: {
  canvas: HTMLCanvasElement;
}) => void | Promise<void>;

import shaderWGSL from "./shaders/shader.wgsl?raw";
const init: SampleInit = async ({ canvas }) => {
  const adapter = await navigator.gpu?.requestAdapter();
  if (!adapter) return;
  const device = await adapter?.requestDevice();
  if (!device) {
    console.error("need a browser that supports WebGPU");
    return;
  }
  const context = canvas.getContext("webgpu");
  if (!context) return;
  const devicePixelRatio = window.devicePixelRatio || 1;
  canvas.width = canvas.clientWidth * devicePixelRatio;
  canvas.height = canvas.clientHeight * devicePixelRatio;
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

  context.configure({
    device,
    format: presentationFormat,
    alphaMode: "premultiplied"
  });
  const shaderModule = device.createShaderModule({
    label: "our hardcoded rgb triangle shaders",
    code: shaderWGSL
  });
  const renderPipeline = device.createRenderPipeline({
    label: "hardcoded rgb triangle pipeline",
    layout: "auto",
    vertex: {
      module: shaderModule,
      entryPoint: "vs"
    },
    fragment: {
      module:shaderModule,
      entryPoint: "fs",
      targets: [
        {
          format: presentationFormat
        }
      ]
    },
    primitive: {
      // topology: "line-list"
      // topology: "line-strip"
      //  topology: "point-list"
      topology: "triangle-list"
      // topology: "triangle-strip"
    }
  });
  function frame() {
    const renderCommandEncoder = device.createCommandEncoder({
      label: "render vert frag"
    });
    if (!context) return;

    const textureView = context.getCurrentTexture().createView();
    const renderPassDescriptor: GPURenderPassDescriptor = {
      colorAttachments: [
        {
          view: textureView,
          clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
          loadOp: "clear",
          storeOp: "store"
        }
      ]
    };
    const renderPass =
      renderCommandEncoder.beginRenderPass(renderPassDescriptor);
    renderPass.setPipeline(renderPipeline);
    renderPass.draw(3, 1, 0, 0);
    renderPass.end();
    const renderBuffer = renderCommandEncoder.finish();
    device.queue.submit([renderBuffer]);

    requestAnimationFrame(frame);
  }

  requestAnimationFrame(frame);
};

const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader:

struct OurVertexShaderOutput {
    @builtin(position) position: vec4f,
    @location(0) color: vec4f
}

@vertex 
fn vs(@builtin(vertex_index) vertexIndex: u32) -> OurVertexShaderOutput {
    let pos = array<vec2f, 3>(
        vec2f(0.0, 0.5), // top center
        vec2f(-0.5, -0.5), // bottom left
        vec2f(0.5, -0.5)  // bottom right
    );
    var color = array<vec4f,3>(
        vec4f(1, 0, 0, 1), // red
        vec4f(0, 1, 0, 1), // green
        vec4f(0, 0, 1, 1) // blue
    );
    var vsOutput: OurVertexShaderOutput;
    vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);
    vsOutput.color = color[vertexIndex];
    return vsOutput;
}

@fragment
fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {
    return fsInput.color;
}

在这里插入图片描述

在这里插入图片描述

阶段间变量最常用于跨三角形插值纹理坐标,我们将在纹理文章中介绍。另一个常见的用法是插值法线穿过三角形,这将在第一篇文章中介绍照明。

阶段变量按位置连接

重要的一点是,就像WebGPU中几乎所有的东西一样,顶点着色器和片段着色器之间的连接是通过索引的。对于阶段间变量,它们通过位置索引连接。
为了了解我的意思,让我们只更改片段着色器,在location(0)处采用vec4f参数,而不是结构体。

     @fragment fn fs(@location(0) color: vec4f) -> @location(0) vec4f {
        return color;
      }

下面两个片段着色器的代码是同等效果的,依然可以渲染出渐变色的三角形。

在这里插入图片描述

@builtin(position)

我们的原始着色器在顶点和片段着色器中使用相同的结构,有一个名为position的字段,但它没有位置。它被声明为@builtin(position)。

      struct OurVertexShaderOutput {
        @builtin(position) position: vec4f,
        @location(0) color: vec4f,
      };

该字段不是阶段间变量。相反,它是内置的。碰巧@builtin(position)在顶点着色器和片段着色器中有不同的含义。

在顶点着色器中,@builtin(position)是GPU在片段着色器中绘制三角形/线/点所需的输出。

在片段着色器中,@builtin(position)是一个输入,是片段着色器当前被要求计算颜色的像素的像素坐标。

像素坐标由像素的边缘指定。提供给片段着色器的值是像素中心的坐标。

如果我们要绘制的纹理大小为3x2像素,这些就是坐标。

在这里插入图片描述

我们可以改变我们的着色器来使用这个位置。例如,让我们画一个棋盘。

const module = device.createShaderModule({
    label: 'our hardcoded checkerboard triangle shaders',
    code: `
      struct OurVertexShaderOutput {
        @builtin(position) position: vec4f,
      };
 
      @vertex fn vs(
        @builtin(vertex_index) vertexIndex : u32
      ) -> OurVertexShaderOutput {
        let pos = array(
          vec2f( 0.0,  0.5),  // top center
          vec2f(-0.5, -0.5),  // bottom left
          vec2f( 0.5, -0.5)   // bottom right
        );
 
        var vsOutput: OurVertexShaderOutput;
        vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);
        return vsOutput;
      }
 
      @fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {
        let red = vec4f(1, 0, 0, 1);
        let cyan = vec4f(0, 1, 1, 1);
 
        let grid = vec2u(fsInput.position.xy) / 8;
        let checker = (grid.x + grid.y) % 2 == 1;
 
        return select(red, cyan, checker);
      }
    `,
  });

position 被声明为@builtin(position),它会将xy坐标转换为vec2u,后者是两个无符号整数。然后将它们除以8,得到每8个像素增加一次的计数。然后,它将x和y网格坐标相加,计算模块2,并将结果与模块1进行比较。这将给我们一个布尔值,每隔一个整数就为true或false。最后,它使用WGSL函数select 给定2个值,根据布尔条件选择其中一个。在JavaScript中,select是这样写的

// If condition is false return `a`, otherwise return `b`
select = (a, b, condition) => condition ? b : a;

代码及运行结果:

HTML:

<!--
 * @Description: 
 * @Author: tianyw
 * @Date: 2022-11-11 12:50:23
 * @LastEditTime: 2023-09-17 16:33:32
 * @LastEditors: tianyw
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>001hello-triangle</title>
    <style>
        html,
        body {
            margin: 0;
            width: 100%;
            height: 100%;
            background: #000;
            color: #fff;
            display: flex;
            text-align: center;
            flex-direction: column;
            justify-content: center;
        }

        div,
        canvas {
            height: 100%;
            width: 100%;
        }
    </style>
</head>

<body>
    <div id="004color-grid-triangle">
        <canvas id="gpucanvas"></canvas>
    </div>
    <script type="module" src="./004color-grid-triangle.ts"></script>

</body>

</html>

TS:

/*
 * @Description:
 * @Author: tianyw
 * @Date: 2023-04-08 20:03:35
 * @LastEditTime: 2023-09-17 21:06:44
 * @LastEditors: tianyw
 */
export type SampleInit = (params: {
  canvas: HTMLCanvasElement;
}) => void | Promise<void>;

import shaderWGSL from "./shaders/shader.wgsl?raw";
const init: SampleInit = async ({ canvas }) => {
  const adapter = await navigator.gpu?.requestAdapter();
  if (!adapter) return;
  const device = await adapter?.requestDevice();
  if (!device) {
    console.error("need a browser that supports WebGPU");
    return;
  }
  const context = canvas.getContext("webgpu");
  if (!context) return;
  const devicePixelRatio = window.devicePixelRatio || 1;
  canvas.width = canvas.clientWidth * devicePixelRatio;
  canvas.height = canvas.clientHeight * devicePixelRatio;
  const presentationFormat = navigator.gpu.getPreferredCanvasFormat();

  context.configure({
    device,
    format: presentationFormat,
    alphaMode: "premultiplied"
  });
  const shaderModule = device.createShaderModule({
    label: "our hardcoded rgb triangle shaders",
    code: shaderWGSL
  });
  const renderPipeline = device.createRenderPipeline({
    label: "hardcoded rgb triangle pipeline",
    layout: "auto",
    vertex: {
      module: shaderModule,
      entryPoint: "vs"
    },
    fragment: {
      module:shaderModule,
      entryPoint: "fs",
      targets: [
        {
          format: presentationFormat
        }
      ]
    },
    primitive: {
      // topology: "line-list"
      // topology: "line-strip"
      //  topology: "point-list"
      topology: "triangle-list"
      // topology: "triangle-strip"
    }
  });
  function frame() {
    const renderCommandEncoder = device.createCommandEncoder({
      label: "render vert frag"
    });
    if (!context) return;

    const textureView = context.getCurrentTexture().createView();
    const renderPassDescriptor: GPURenderPassDescriptor = {
      colorAttachments: [
        {
          view: textureView,
          clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
          loadOp: "clear",
          storeOp: "store"
        }
      ]
    };
    const renderPass =
      renderCommandEncoder.beginRenderPass(renderPassDescriptor);
    renderPass.setPipeline(renderPipeline);
    renderPass.draw(3, 1, 0, 0);
    renderPass.end();
    const renderBuffer = renderCommandEncoder.finish();
    device.queue.submit([renderBuffer]);

    requestAnimationFrame(frame);
  }

  requestAnimationFrame(frame);
};

const canvas = document.getElementById("gpucanvas") as HTMLCanvasElement;
init({ canvas: canvas });

Shaders:

shader:

struct OurVertexShaderOutput {
    @builtin(position) position: vec4f
}

@vertex 
fn vs(@builtin(vertex_index) vertexIndex: u32) -> OurVertexShaderOutput {
    let pos = array<vec2f, 3>(
        vec2f(0.0, 0.5), // top center
        vec2f(-0.5, -0.5), // bottom left
        vec2f(0.5, -0.5)  // bottom right
    );
    var vsOutput: OurVertexShaderOutput;
    vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);
    return vsOutput;
}

@fragment
fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f {
    let red = vec4f(1,0,0,1);
    let cyan = vec4f(0, 1, 1, 1);

    let grid = vec2u(fsInput.position.xy) / 8;
    let checker = (grid.x + grid.y) % 2 == 1;
    return select(red, cyan, checker);
}

在这里插入图片描述

在这里插入图片描述

即使你在片段着色器中不使用@builtin(position),它的存在也很方便,因为它意味着我们可以在顶点着色器和片段着色器中使用同一个结构体。需要注意的是,顶点着色器和片段着色器中的position结构体字段是完全不相关的。它们是完全不同的变量。

如上所述,对于阶段间变量,重要的是@location(?)。因此,为顶点着色器的输出和片段着色器的输入声明不同的结构体是很常见的。

为了让这个更清楚,在我们的例子中,顶点着色器和片段着色器在同一个字符串中只是为了方便。我们也可以将它们分成单独的模块。

const vsModule = device.createShaderModule({
    label: 'hardcoded triangle',
    code: `
      struct OurVertexShaderOutput {
        @builtin(position) position: vec4f,
      };
 
      @vertex fn vs(
        @builtin(vertex_index) vertexIndex : u32
      ) -> OurVertexShaderOutput {
        let pos = array(
          vec2f( 0.0,  0.5),  // top center
          vec2f(-0.5, -0.5),  // bottom left
          vec2f( 0.5, -0.5)   // bottom right
        );
 
        var vsOutput: OurVertexShaderOutput;
        vsOutput.position = vec4f(pos[vertexIndex], 0.0, 1.0);
        return vsOutput;
      }
    `,
  });
 
  const fsModule = device.createShaderModule({
    label: 'checkerboard',
    code: `
      @fragment fn fs(@builtin(position) pixelPosition: vec4f) -> @location(0) vec4f {
        let red = vec4f(1, 0, 0, 1);
        let cyan = vec4f(0, 1, 1, 1);
 
        let grid = vec2u(pixelPosition.xy) / 8;
        let checker = (grid.x + grid.y) % 2 == 1;
 
        return select(red, cyan, checker);
      }
    `,
  });

我们必须更新创建的管道才能使用它们

  const pipeline = device.createRenderPipeline({
    label: 'hardcoded checkerboard triangle pipeline',
    layout: 'auto',
    vertex: {
      module: vsModule,
      entryPoint: 'vs',
    },
    fragment: {
      module: fsModule,
      entryPoint: 'fs',
      targets: [{ format: presentationFormat }],
    },
  });
 

这里 demo 只更改了 fragment 的代码,效果等同:

在这里插入图片描述

关键是,在大多数WebGPU示例中,两个着色器使用相同的字符串只是为了方便。实际上,首先WebGPU解析WGSL以确保其语法正确。然后,WebGPU查看你指定的入口点。从那里开始,它会查看入口点引用的部分,而不是该入口点的其他部分。它很有用,因为如果两个或多个着色器共享绑定、结构、常量或函数,就不需要两次输入结构、绑定和分组位置等内容。但是,从WebGPU的角度来看,就好像您为每个入口点复制了所有它们一样。

注意:使用@builtin(position)生成棋盘并不常见。棋盘或其他图案更常用纹理来实现。实际上,如果调整窗口的大小,就会出现问题。因为棋盘是基于画布的像素坐标,所以它是相对于画布的,而不是相对于三角形的。

插值设置

我们在上面看到,阶段间变量,顶点着色器的输出,在传递给片段着色器时进行插值。有两组设置可以改变插值的发生方式。将它们设置为默认值以外的任何值并不常见,但有一些用例将在其他文章中介绍。

插值类型:

  • perspective:值以正确的透视方式(默认)插值。
  • linear:值以线性的、非透视的正确方式插值。
  • falt:值不进行插值。插值采样不用于平面插值

插值采样(Interpolation sampling):

  • center:在像素的中心执行插值(默认)
  • centroid:在当前基元内的碎片覆盖的所有样本内的一点执行插值。这个值对于原始类型中的所有样本都是相同的。
  • sample:对每个样本进行插值。应用这个属性时,每个样本都会调用一次片段着色器。

将它们指定为属性。例如:

@location(2) @interpolate(linear, center) myVariableFoo: vec4f;
  @location(3) @interpolate(flat) myVariableBar: vec4f;

请注意,如果阶段间变量是整数类型,则必须将其插值设置为平坦 flat。

如果将插值类型设置为flat,则传递给片段着色器的值就是该三角形中第一个顶点的 变量的值。

在下一篇文章中,我们将介绍uniform作为传递数据到着色器的另一种方法。

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

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

相关文章

科技资讯|苹果下一代Vision Pro头显将更小更轻,预装处方镜片

据彭博社的 Mark Gurman 在《Power On》新闻简报中透露&#xff0c;苹果和 Meta 的混合现实头显还未发售&#xff0c;但两家的下一代机型的开发工作已经在顺利进行。 据报道&#xff0c;苹果下代产品的一个重点是通过更小、更轻的设计&#xff0c;使其设备佩戴起来更加舒适。据…

VScode远程root权限调试

尝试诸多办法无法解决的情况下&#xff0c;允许远程登陆用户直接以root身份登录 编辑sshd_config文件 sudo vim /etc/ssh/sshd_config 激活配置 注释掉PermitRootLogin without-password&#xff0c;即#PermitRootLogin without-password 增加一行&#xff1a;PermitRootLo…

浅谈高速公路服务区分布式光伏并网发电

前言 今年的国家经济工作会议提出&#xff1a;将“做好碳达峰、碳中和工作”作为 2021年的主要任务之一&#xff0c;而我国高速公路里程 15.5万公里&#xff0c;对能源的需求与日俱增&#xff0c;碳排放量增速明显。 为了实现采用减少碳排放量&#xff0c;采用清洁能源替代的…

mac版postman升级后数据恢复办法

postman升级了一下&#xff0c;所有的collections都丢失了。 首先在finder里找到这个路径 /Users/{用户名}/Library/Application Support/Postman找到升级之前的的最新的backup.json&#xff0c;然后在postman里import这个文件。 所有升级前的collections都恢复了&#xff0…

云上攻防-云服务篇对象存储Bucket桶任意上传域名接管AccessKey泄漏

文章目录 章节点前言对象存储各大云名词&#xff1a; 对象存储-以阿里云为例&#xff1a;正常配置权限配置错误公共读或公共读写&#xff1a;可完整访问但不显示完整结构权限Bucket授权策略&#xff1a;设置ListObject显示完整结构权限Bucket读写权限&#xff1a;公共读写直接P…

数据结构相关知识点(一)

一、线性表 1.什么是线性表&#xff1f; 线性表&#xff0c;全名为线性存储结构。使用线性表存储数据的方式可以理解理解为&#xff1a;把所有数据按照顺序&#xff08;线性&#xff09;的存储结构方式&#xff0c;存储在物理空间。 2.线性存储结构 顺序存储结构&#xff1…

建筑行业+办公领域低代码解决方案:创新、效率与商机的共赢

随着科技的不断进步和创新&#xff0c;建筑行业面临着技术创新和转型升级的压力。新的技术和工艺不断涌现&#xff0c;建筑企业需要紧跟技术趋势&#xff0c;引入新的技术和工艺&#xff0c;提高生产效率和质量&#xff0c;增强自身的核心竞争力。 而且建筑行业是一个高度竞争…

MongoDB副本集集群原理

文章目录 1、副本集-Replica Sets1.1、是什么1.2、副本集的三个角色1.3、副本集架构目标1.4、副本集的创建1.4.1 第一步&#xff1a;创建主节点1.4.2 第二步&#xff1a;创建副本节点1.4.3 第三步&#xff1a;创建仲裁节点1.4.4 第四步&#xff1a;初始化配置副本集和主节点1.4…

基于SSM的影视企业全渠道会员管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

计算机毕业设计选什么题目好?springboot 仓库在线管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

400电话是什么电话,和普通电话有什么不同

400电话是一种特殊的电话号码&#xff0c;通常用于企业或机构提供客户服务和咨询。与普通电话相比&#xff0c;400电话有以下几个不同之处。 首先&#xff0c;400电话是一种虚拟号码&#xff0c;不依赖于地理位置。普通电话号码通常与特定的地区或城市相关联&#xff0c;而400…

HP ENVY x360 Convert 15-bp002tx,15-bp106TX原厂Windows10系统

惠普笔记本ENVY X360原装出厂OEM预装Win10镜像文件 下载链接&#xff1a;https://pan.baidu.com/s/1GXoEIHaJtP752zYDJknrJg?pwdmq35 适用型号&#xff1a;15-bp001tx,15-bp002tx,15-bp003tx,15-bp004tx,15-bp005tx,15-bp006tx 15-bp101TX,15-bp102tx,15-bp103tx,15-bp104t…

跨境商城源码的终极秘密:寻找最佳解决方案的5个步骤!

在当今全球化的商业环境下&#xff0c;跨境电商已经成为许多企业拓展市场的重要战略之一。而搭建一个高效稳定的跨境商城平台是成功的关键所在。但是&#xff0c;要找到最佳解决方案并不容易。本文将详细介绍寻找跨境商城源码的最佳解决方案的五个步骤&#xff0c;帮助您在选择…

【ppt技巧】ppt里的图片如何提取出来?

之前分享过如何将PPT文件导出成图片&#xff0c;今天继续分享PPT技巧&#xff0c;如何提取出PPT文件里面的图片。 首先&#xff0c;我们将PPT文件的后缀名&#xff0c;修改为rar&#xff0c;将文件改为压缩包文件 然后我们将压缩包文件进行解压 最好是以文件夹的形式解压出来…

力扣 -- 1143. 最长公共子序列

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int longestCommonSubsequence(string s1, string s2) {int ms1.size();int ns2.size();s1 s1;s2 s2;vector<vector<int>> dp(m1,vector<int>(n1));for(int i1;i<m;i){for(int j1;j&…

【计算机毕设案例推荐】洋州影院购票管理系统SpringBoot+Vue

前言&#xff1a;我是IT源码社&#xff0c;从事计算机开发行业数年&#xff0c;专注Java领域&#xff0c;专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务 项目名 基于SpringBoot的洋州影院购票管理系统 技术栈 SpringBootVueMySQLMaven 文章目录 一、洋州…

windows cmd下查看环境变量的信息

在windows cmd窗口下&#xff0c;怎么查看环境变量的值&#xff1f; 直接输入set命令然后回车&#xff0c;即可看到所有环境变量的信息&#xff0c;例如&#xff1a; 输入set <变量名>然后回车&#xff0c;可以查看某个环境变量的值&#xff0c;例如set path&#xff1a…

【网络安全】「漏洞原理」(二)SQL 注入漏洞之理论讲解

前言 严正声明&#xff1a;本博文所讨论的技术仅用于研究学习&#xff0c;旨在增强读者的信息安全意识&#xff0c;提高信息安全防护技能&#xff0c;严禁用于非法活动。任何个人、团体、组织不得用于非法目的&#xff0c;违法犯罪必将受到法律的严厉制裁。 【点击此处即可获…

Python使用MySQL,无记录则插入,有记录则更新 - ON DUPLICATE KEY UPDATE

一、基本语法 ON DUPLICATE KEY UPDATE 语句基本功能是&#xff1a;当表中没有原来记录时&#xff0c;就插入&#xff0c;有的话就更新。 使用注意事项如下&#xff1a; ON DUPLICATE KEY UPDATE语句根据主键id或唯一键来判断当前插入是否已存在。记录已存在时&#xff0c;只…

antd pro form 数组套数组 form数组动态赋值 shouldUpdate 使用

antd form中数组套数组 form数组动态变化 动态赋值 需求如上&#xff0c;同时添加多个产品&#xff0c;同时每个产品可以增加多台设备&#xff0c;根据设备增加相应编号&#xff0c;所以存在数组套数组&#xff0c;根据数组值动态变化 使用的知识点 form.list form中的数组…