二、WebGPU阶段间变量(inter-stage variables)
二、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 centervec2f(-0.5, -0.5), // bottom leftvec2f( 0.5, -0.5) // bottom right);var color = array<vec4f, 3>(vec4f(1, 0, 0, 1), // redvec4f(0, 1, 0, 1), // greenvec4f(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), // redvec4f(0, 1, 0, 1), // greenvec4f(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 centervec2f(-0.5, -0.5), // bottom leftvec2f(0.5, -0.5) // bottom right);var color = array<vec4f,3>(vec4f(1, 0, 0, 1), // redvec4f(0, 1, 0, 1), // greenvec4f(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 centervec2f(-0.5, -0.5), // bottom leftvec2f( 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 centervec2f(-0.5, -0.5), // bottom leftvec2f(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 centervec2f(-0.5, -0.5), // bottom leftvec2f( 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作为传递数据到着色器的另一种方法。
相关文章:

二、WebGPU阶段间变量(inter-stage variables)
二、WebGPU阶段间变量(inter-stage variables) 在上一篇文章中,我们介绍了一些关于WebGPU的基础知识。在本文中,我们将介绍阶段变量(inter-stage variables)的基础知识。 阶段变量在顶点着色器和片段着色…...
【Linux】31个普通信号
文章目录 1.每种信号的含义2.两种不能被忽略的信号3.两种不能被捕捉的信号 1.每种信号的含义 信号编号信号名信号含义1SIGHUP如果终端接口检测到一个连接断开,则会将此信号发送给与该终端相关的控制进程,该信号的默认处理动作是终止进程。2SIGINT当用户…...

Mac电脑交互式原型设计 Axure RP 8汉化最新 for mac
Axure RP 8是一款专业且快速的原型设计工具,主要用于定义需求、规格、设计功能和界面。这款工具主要适用于用户体验设计师、交互设计师、业务分析师、信息架构师、可用性专家和产品经理等职业。 Axure RP 8的主要特性包括能够快速设计出应用软件或Web网站的线框图、…...

在线免费无时长限制录屏工具 - 录猎在线版
需要录屏的小伙伴注意啦,想要长时间录制又不想花钱的,可以看下这款在线版录屏软件 —— 录猎在线版,一个录屏软件所需要的基本功能它都有,设置录制范围、录制的声音来源、摄像头也能录制的。同时它是支持Windows和Mac系统的&#…...

c语言文件操作详解:fgetc,fputc,fgets,fputs,fscanf,,fprintf,fread,fwrite的使用和区别
前言:在对于c语言的学习中,我们为了持续使用一些数据,为了让我们的数据可以在程序退出后仍然保存并且可以使用,我们引入了文件的概念和操作,本文旨在为大家分享在文件操作中常用的输入输出函数的使用方式和技巧&#x…...
Harmony装饰器
1、装饰器 装饰器是用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如: Component表示自定义组件Entry表示该自定义组件为入口组件State表示组件中的状态变量,状态变量变化会触发UI刷新。 2 、语法范式 Builder/BuilderParam&#…...

如何加快Chrome谷歌浏览器下载速度?
用Chrome打开chrome://flags/...

使用kubectl连接远程Kubernetes(k8s)集群
使用kubectl连接远程Kubernetes集群 环境准备下载kubectl下载地址 安装kubectl并处理配置文件Windows的安装配置安装kubectl拉取配置文件安装kubectl拉取配置文件kubectl命令自动补全 Linux的安装配置安装kubectl拉取配置文件kubectl命令自动补全 环境准备 你需要准备一个Kube…...

Kubernetes革命:云原生时代的应用编排和自动化
文章目录 什么是Kubernetes以及为何它备受欢迎?云原生应用和K8s的关系Kubernetes的核心概念:Pods、Services、ReplicaSets等部署、扩展和管理应用程序的自动化容器编排的演进:Docker到Kubernetes实际用例:企业如何受益于K8s的应用…...
mysql.mongoDb,neo4j数据库对比
#Mysql与MongoDb和Neo4j的一些对比 主要区别 MySQL: 1.MySQL是一种关系型数据库管理系统(RDBMS),广泛用于处理结构化数据。 2.它支持SQL语言,具备成熟的事务处理和数据一致性能力。 3.MySQL适用于大多数传统的基于表…...

unity使用UniStorm 5.1.0.unitypackage增加天气
添加天天气组件unistorm 然后添加一个player 导入包会报错,需要修改代码 using UnityEngine; using UnityEngine.PostProcessing;namespace UnityEditor.PostProcessing {[CustomPropertyDrawer(typeof(UnityEngine.PostProcessing.MinAttribute))]sealed class MinDrawer : …...

Flink实现kafka到kafka、kafka到doris的精准一次消费
1 流程图 2 Flink来源表建模 --来源-城市topic CREATE TABLE NJ_QL_JC_SSJC_SOURCE ( record string ) WITH (connector kafka,topic QL_JC_SSJC,properties.bootstrap.servers 172.*.*.*:9092,properties.group.id QL_JC_SSJC_NJ_QL_JC_SSJC_SOURCE,scan.startup.mode …...

Outlook屏蔽Jira AI提醒
前言:最近不知道为什么jira上的ai小助手抽风,一周发个几千封邮件…导致我现在都不想在邮箱里面跟找垃圾一样找消息了。实在忍无可忍,决定屏蔽AI小助手,方法很简单,follow me~~ 第一步:双击打开电脑版Outloo…...

毛玻璃 has 选择器卡片悬停效果
效果展示 页面结构 从上述的效果展示可以看到,页面是由多个卡片组成,并且鼠标悬停在卡片上时,会旋转用户图片并且韩式对应的用户信息框。 CSS3 知识点 :has 属性的运用 实现页面整体结构 <div class"container"><div…...
[hive]解决group by 字段超过系统规定64个
用开窗函数即可 ( row_number() over(partition by col1,...,col70 oder by xx) rn ) where rn1...
生成老年人的声音sox
sox laoren1.wav laoren2.wav pitch -300...

DC2DC电源设计注意事项--1,Feedback
电源采集图如下图 Feedback 采集电压点应该在靠近负载侧。这样可以减少大电流导线导致的电压差,真实反应输出电压值 FB_1P21采集电路靠近芯片侧, 2.1,采集分压电路上侧为Vout Vnoise, 那么一分压就噪声就小了。假如采集电路远离芯片侧&…...

计算机视觉处理的开源框架
计算机视觉是一门涉及图像和视频分析的领域,有许多开源的框架和库可用于构建计算机视觉应用程序。以下是一些常见的计算机视觉开源框架及其特点,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合…...

最新AI智能创作系统源码AI绘画系统/支持GPT联网提问/支持Prompt应用
AI绘图专业设计 不得将程序用作任何违法违纪内容,不要让亲人两行泪 界面部分图解构: 前台show: 前端部署: 安装pm2管理器 点击设置 选择v16.19.1版本-切换版本 再新建一个网站 点击设置 添加反向代理-代理名称随便…...
2019架构真题案例(四十八)
系统应用集成构件统一标准的基础平台,在各个应用系统的接口之间数据共享和功能,基本原则是保证应用程序的()。系统应用集成提供了四个不同层次的服务,最上层服务是()。 独立性相关性互操作性排…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
聊一聊接口测试的意义有哪些?
目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开,首…...

图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...