【WebGPU】WebGPU 中的反应扩散计算着色器

在本教程中,我们将使用 WebGPU 技术中的计算着色器实现图像效果。更多精彩内容尽在数字孪生平台。

image.png

程序结构

主要构建两个 WebGPU 管道:

  • 运行反应扩散算法多次迭代的计算管道(js/rd-compute.jsjs/shader/rd-compute-shader.js
  • 渲染管道,它获取计算管道的结果并通过渲染全屏三角形(js/composite.jsjs/shader/composite-shader.js)来创建最终合成图像。

WebGPU 是一个非常繁琐的 API,为了使其更容易使用,我使用了 webgpu-utils 库。此外,还包含 float16 库,用于创建和更新计算管道的存储纹理。

计算管道流程

在 GPU 上运行反应扩散模拟的一种常见方法是使用纹理交替。就是创建两个纹理,一个纹理保存要读取的模拟的当前状态,另一个纹理存储当前迭代的结果。每次迭代后,纹理都会交换。

此方法也可以使用片段着色器和帧缓冲在 WebGL 中实现。但是在 WebGPU 中,我们可以使用计算着色器和存储纹理作为缓冲区来实现相同的效果。这样做的优点是我们可以直接写入我们想要的纹理内的任何像素,还获得了计算着色器带来的性能优势。

初始化

首先是使用所有必要的布局描述符初始化管道。此外,还必须设置所有的缓冲区、纹理和绑定组。webgpu-utils 库就可以在这里节省大量工作。

WebGPU 不允许在创建缓冲区或纹理后更改其大小。因此,我们必须区分大小不变的缓冲区(例如uniform)和在某些情况下发生变化的缓冲区(例如调整画布大小时的纹理)。对于后者,我们需要一种方法来重新创建它们并在必要时销毁旧的缓冲区。

用于反应扩散模拟的所有纹理都是画布大小的一小部分(例如画布大小的四分之一)。要处理的像素数量较少,可以释放计算资源以进行更多迭代。因此,可以以相对较小的视觉损失进行更快的模拟。

除了“纹理交换”中涉及的两个纹理之外,示例中还有第三个纹理,我将其称为种子纹理。此纹理包含在其上绘制时钟字母的 HTML 画布的图像数据。种子纹理用作反应扩散模拟的一种影响图,以可视化时钟字母。当 WebGPU 画布调整大小时,必须重新创建该纹理以及相应的 HTML 画布大小调整。

运行模拟

完成所有必要的初始化后,我们可以使用计算着色器实际运行反应扩散模拟。我们先回顾一下计算着色器的一些特性。

计算着色器的每次调用都会并行处理多个线程。线程数由计算着色器的工作组(workgroup)大小定义。着色器的调用次数由调度(dispatch)大小定义(线程总数 = 工作组大小 * 调度大小)。

这些值以三个维度指定。因此,并行处理 64 个线程的计算着色器可能如下所示:

@compute @workgroup_size(8, 8, 1) fn compute() {}

运行此着色器 256 次(即 16,384 个线程)需要如下的调度大小:

pass.dispatchWorkgroups(16, 16, 1);

反应扩散模拟要求我们处理纹理的每个像素。实现此目的的一种方法是使用 workgroup 大小为 1 和 dispatch大小等于像素总数(像是模仿片段着色器)。但是这样不会提高性能,因为 workgroup 中的多个线程比单独的调度更快。

另一方面,我们可能想到使用等于像素数的 workgroup 大小,并且仅调用一次(dispatch 大小为 1)。然而这是不可能的,因为最大 workgroup 大小是有限的。对于 WebGPU 的一般建议是选择 workgroup 大小为 64。这要求我们将纹理内的像素数量划分为 workgroup 大小(= 64 像素)的块,并经常调度工作组以覆盖整个纹理。

因此,现在我们有了 workgroup 大小的恒定值,并且能够找到适当的 dispatch 大小来运行我们的模拟。但是其实我们还有更多可以优化的地方。

每线程像素数

为了使每个workgroup覆盖更大的区域(更多像素),我们引入了图块大小。图块大小定义每个单独线程处理的像素数,这就需要我们在着色器中使用嵌套 for 循环,所以我们需要保持图块大小非常小(例如 2×2)。

像素缓存

运行反应扩散模拟的一个重要步骤是与拉普拉斯核(3×3 矩阵)进行卷积。因此,对于我们处理的每个像素,我们必须读取内核覆盖的所有 9 个像素才能执行计算。由于像素与像素之间的内核重叠,因此会出现大量冗余纹理读取。

幸运的是,计算着色器允许我们跨线程共享内存。所以我们可以创建像素缓存。这个方式(来自图像模糊示例)是每个线程读取其图块的像素并将它们写入缓存。一旦workgroup的每个线程都将其像素存储在缓存中(我们通过工作组屏障确保这一点),实际处理只需要使用从缓存中预取的像素。因此它不需要任何进一步的纹理读取。计算函数的结构可能如下所示:

// workgroup所有线程共享的像素缓存
var<workgroup> cache: array<array<vec4f, 128>, 128>;@compute @workgroup_size(8, 8, 1)
fn compute_main(/* ...builtin variables */ ) {// 将此线程的图块的像素添加到缓存中for (var c=0u; c<2; c++) {for (var r=0u; r<2; r++) {// ... 从内置变量计算像素坐标// 将像素值存储在缓存中cache[y][x] = value;}}// 在所有线程都到达此点之前不要继续workgroupBarrier();// 处理该线程图块的每个像素for (var c=0u; c<2; c++) {for (var r=0u; r<2; r++) {// ... 执行反应扩散算法textureStore(/* ... */);}}}
}

但我们还必须注意另一个棘手的问题:内核卷积要求我们读取比最终处理的像素更多的像素。我们可以扩展像素缓存大小,但是workgroup线程共享的内存大小限制为 16,384 字节。因此,我们必须将每一侧的dispatch大小减少 (kernelSize - 1)/2。下面的插图可以让这些步骤更加清晰:
image.png

UV扰动

与片段着色器解决方案相比,使用计算着色器的一个缺点是无法在计算着色器中使用采样器来存储纹理(只能加载整数像素坐标)。如果想通过移动纹理空间(即以小数增量扰动 UV 坐标)来对模拟进行动画处理,则必须自己进行采样。

解决这个问题的一种方法是使用手动双线性采样函数。示例中使用的采样函数基于此处所示的采样函数,并进行了一些调整以供在计算着色器中使用。这允许我们对浮点像素值进行采样:

fn texture2D_bilinear(t: texture_2d<f32>, coord: vec2f, dims: vec2u) -> vec4f {let f: vec2f = fract(coord);let sample: vec2u = vec2u(coord + (0.5 - f));let tl: vec4f = textureLoad(t, clamp(sample, vec2u(1, 1), dims), 0);let tr: vec4f = textureLoad(t, clamp(sample + vec2u(1, 0), vec2u(1, 1), dims), 0);let bl: vec4f = textureLoad(t, clamp(sample + vec2u(0, 1), vec2u(1, 1), dims), 0);let br: vec4f = textureLoad(t, clamp(sample + vec2u(1, 1), vec2u(1, 1), dims), 0);let tA: vec4f = mix(tl, tr, f.x);let tB: vec4f = mix(bl, br, f.x);return mix(tA, tB, f.y);
}

这就是示例中所示的从中心开始的模拟脉动运动的创建方式。

合成渲染

反应扩散模拟完成后,唯一剩下的就是将结果绘制到屏幕上。这是合成渲染管道的工作。

我这里简要概述示例程序中涉及的步骤:

  1. 凸出变形:在对反应扩散结果纹理进行采样之前,将凸出变形应用于 UV 坐标(基于此 Shadertoy 代码),可以增加场景的深度感。
  2. 颜色:应用调色板(来自 Inigo Quilez)
  3. 浮雕滤镜:简单的浮雕效果赋予“纹理”一定的体积。
  4. 假彩虹色:这种微妙的效果基于不同的调色板,但应用于压花结果的负空间。假虹彩使场景看起来更加充满活力。
  5. 晕影:晕影叠加用于使边缘变暗。

image.png

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

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

相关文章

script标签以及defer和async属性

1. <script>标签 将JavaScript代码嵌入到HTML中主要方式是使用<script>元素。 使用<script>的方式有两种&#xff1a; &#xff08;1&#xff09;直接在网页中嵌入JavaScript代码&#xff1a; <script>function sayHi() {console.log("Hi"…

Leetcode—2244. 完成所有任务需要的最少轮数【中等】

2024每日刷题&#xff08;136&#xff09; Leetcode—2244. 完成所有任务需要的最少轮数 实现代码 class Solution { public:int minimumRounds(vector<int>& tasks) {unordered_map<int, int> map;for(int task: tasks) {map[task];}int ans 0;// freq 1 …

【前端】CSS基础(3)

文章目录 前言1. CSS常用元素属性1.1 字体属性1.1.1 字体1.1.2 字体大小1.1.3 字体颜色1.1.4 字体粗细1.1.5 文字样式 前言 这篇博客仅仅是对CSS的基本结构进行了一些说明&#xff0c;关于CSS的更多讲解以及HTML、Javascript部分的讲解可以关注一下下面的专栏&#xff0c;会持续…

c++父类指针指向子类

有一个常见的c题&#xff0c;就是父类和子类的构造函数和析构函数分别调用顺序&#xff1a; 父类构造函数子类构造函数子类析构函数父类析构函数 以及父类中的函数在子类中重新实现后&#xff0c;父类指针指向子类后&#xff0c;该指针调用的函数是父类中的还是子类中的&…

震撼发布!GPT-4o 上线!

5 月 14日凌晨一点&#xff0c;OpenAI 发布了 GPT-4o&#xff01; 新模型的功能简单概括就是&#xff1a;更快、更智能、更像人类。 秉承着持续更新的态度&#xff0c;Hulu AI 快速接入 GPT-4o 啦&#xff01; 继 5 月份上线 Suno 之后&#xff0c;这次是 Hulu AI 的又一重大…

vue3专栏项目 -- 六、上传组件(上)

1、上传组件需求分析 我们还需要新建和展示文章&#xff0c;新建文章自然是发送post请求&#xff0c;同时在post中自带对应的数据&#xff0c;展示文章就是根据id取出已有的数据并且展示出来。 这里有一个难点就是上传组件&#xff0c;上传文件是App应用中最基本的需求&#…

Socks5:网络世界的隐形斗篷

在数字化时代&#xff0c;网络隐私和安全已成为人们日益关注的话题。Socks5&#xff0c;作为一种代理协议&#xff0c;为用户在网络世界中的匿名性提供了强有力的支持。本文将从Socks5的多个方面&#xff0c;深入探讨这一技术如何成为网络世界的“隐形斗篷”。 一、Socks5的基本…

linux基础指令讲解(ls、pwd、cd、touch、mkdir)

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 总有光环在陨落&#xff0c;总有新星在闪烁 这个是我们今天要用到的初始…

P8805 [蓝桥杯 2022 国 B] 机房

P8805 [蓝桥杯 2022 国 B] 机房 分析 是一道lca题目&#xff0c;可以直接套模板 前缀和处理点权 具体思路&#xff1a; 1.n台电脑用n-1条网线相连&#xff0c;任意两个节点之间有且仅有一条路径&#xff08;拆分成各自到公共祖先节点的路径——lca&#xff09;&#xff1b;…

波搜索算法(WSA)-2024年SCI新算法-公式原理详解与性能测评 Matlab代码免费获取

​ 声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 原理简介 一、初始化阶段 二、全…

我与C++的爱恋:string类的常见接口函数

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;我与C的爱恋 朋友们大家好啊&#xff0c;本节我们来到STL内容的第一部分&#xff1a;string类接口函数的介绍 ​ ​ 1.string类的认识 给大家分享一个c文档 https://legacy.cplusplus.…

Weblogic 管理控制台未授权远程命令执行漏洞(CVE-2020-14882,CVE-2020-14883)

1 漏洞概述 Weblogic Pre-Auth Remote Command Execution 漏洞&#xff08;CVE-2020-14882, CVE-2020-14883&#xff09;是针对 Oracle WebLogic Server 的两个安全漏洞。CVE-2020-14882 允许远程用户绕过管理员控制台组件中的身份验证&#xff0c;而 CVE-2020-14883 则允许经…

Sam Blackshear谈Move语言的起源

Move编程语言作为Sui生态系统的关键组成部分&#xff0c;通过可编程交易区块等机制支持其独特的对象数据模型&#xff0c;并支持高效的代码。五年前&#xff0c;Mysten Labs的联合创始人兼首席技术官Sam Blackshear创建了Move。他专门设计了Move&#xff0c;用于编写智能合约&a…

sqli-labs靶场第十四关

目录 1&#xff1a;分析 找闭合符&#xff1a; 2&#xff1a;开始注入 报错注入&#xff1a; 注入数据库名&#xff1a; 注入表名&#xff1a; 注入列名&#xff1a; 注入具体值&#xff1a; 1&#xff1a;分析 经过我们的实验发现当我们输入的密码后面存在双引号时会报…

【C++】学习笔记——多态_1

文章目录 十二、继承8. 继承和组合 十三、多态1. 多态的概念2. 多态的定义和实现虚函数重写的两个特殊情况override 和 final 3. 多态的原理1. 虚函数表 未完待续 十二、继承 8. 继承和组合 我们已经知道了什么是继承&#xff0c;那组合又是什么&#xff1f;下面这种情况就是…

英语学习笔记13——A new dress

A new dress 一件新连衣裙 词汇 Vocabulary colour / color n. 颜色 v. 上色&#xff0c;涂色  英  美 颜色短语&#xff1a;green hand 新手      black tea 红茶      white house 白宫      black sheep 害群之马 英文颜色类词汇&#xff1a; red 红色…

鸿蒙开发接口Ability框架:【ApplicationContext】

ApplicationContext ApplicationContext模块提供开发者应用级别的的上下文的能力&#xff0c;包括提供注册及取消注册应用内组件生命周期的监听接口。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.m…

静态IP代理:网络世界的隐秘通道

在数字化时代&#xff0c;网络安全和隐私保护日益受到重视。静态IP代理作为一种网络服务&#xff0c;为用户提供了一个稳定且可预测的网络连接方式&#xff0c;同时保护了用户的在线身份。本文将从五个方面深入探讨静态IP代理的概念、优势、应用场景、技术实现以及选择时的考量…

C语言学习【printf函数和scanf函数】

C语言学习【printf函数和scanf函数】 printf()函数和scanf()函数可以让用户与程序交流&#xff0c;是输入/输出函数 printf()函数 请求printf()函数打印数据的指令要与待打印数据的类型相匹配。例如&#xff0c;打印整数时使用%d&#xff0c;打印字符时使用%c。这些符号被称…

程序在银河麒麟系统下实现开机自启及创建桌面快捷方式

目录 1. 机器环境说明 2. 程序开机自启动设置 2.桌面快捷方式设置 3. 附加说明 1. 机器环境说明 机器安装的银河麒麟操作系统属性如下&#xff1a; 2. 程序开机自启动设置 第1步&#xff1a;编写一个脚本,用于自动化启动&#xff0c;为便于后文描述&#xff0c;该脚本名称…