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

news2024/12/23 18:15:46

在本教程中,我们将使用 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.coloradmin.cn/o/1667065.html

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

相关文章

java项目之汽车资讯网站源码(springboot+mysql+vue)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的汽车资讯网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 汽车资讯网站的主要使用者管…

小学拼音弄一下

import re from xpinyin import Pinyindef remove_middle_characters(text):# 仅保留汉字chinese_chars re.findall(r[\u4e00-\u9fff], text)cleaned_text .join(chinese_chars)# 如果字符数为偶数&#xff0c;则在中间添加空格if len(cleaned_text) % 2 0:middle_index le…

maven找不到依赖,in offline mode

问题描述&#xff1a; [ERROR] Plugin org.jetbrains.kotlin:kotlin-maven-plugin:1.2.71 or one of its dependencies could not be resolved: Failed to read artifact descriptor for org.jetbrains.kotlin:kotlin-maven-plugin:jar:1.2.71: Cannot access aliyunmaven (ht…

JVM从1%到99%【精选】-类加载子系统

目录 1.类的生命周期 1.加载 2.连接 3.初始化 2.类的加载器 1.类加载器的分类 2.双亲委派机制 3.面试题&#xff1a;类的双亲委派机制是什么&#xff1f; 4.打破双亲委派机制 1.类的生命周期 类加载过程&#xff1a;加载、链接&#xff08;验证、准备、解析&a…

VMware Workstation 16 Pro安装教程

文章目录 1、下载2、安装 1、下载 复制到迅雷下载&#xff1a;https://download3.vmware.com/software/wkst/file/VMware-workstation-full-16.0.0-16894299.exe 2、安装 秘钥&#xff1a; ZF3R0-FHED2-M80TY-8QYGC-NPKYF YF390-0HF8P-M81RQ-2DXQE-M2UT6 ZF71R-DMX85-08DQY-…

易图讯科技三维电子沙盘系统

深圳易图讯科技有限公司&#xff08;www.3dgis.top&#xff09;创立于2013年&#xff0c;专注二三维地理信息、三维电子沙盘、电子地图、虚拟现实、大数据、物联网和人工智能技术研发&#xff0c;获得20多项软件著作权和软件检测报告&#xff0c;成功交付并实施了1000多个项目&…

商业数据分析--时间序列图及趋势分析

绘制时间序列图,并指出存在什么样的状态如上两图: 可见状态:从时间序列图可以看出,这些数据存在明显的季节性波动,每年的第4季度值都最高,而第2季度值最低。同时也存在一些下降的趋势。 通过引进虚拟变量,建立多元线性回归模型。答: 通过引入虚拟变量,我们可以建立如下的…

【初级数据结构】队列

目录 前言队列的概念及结构队列的实现队列的结构队列的初始化队列的销毁入队出队取队头元素取队尾元素判断队列是否为空取出队列中元素个数代码测试 完整代码Queue.hQueue.ctest.c 前言 前面我们已经学习了栈&#xff0c;栈是一种后进先出的结构&#xff0c;即LIFO&#xff0c;…

22、Flink 背压下的 Checkpoint处理

1.概述 通常&#xff0c;对齐 Checkpoint 的时长主要受 Checkpointing 过程中的同步和异步两个部分的影响&#xff1b;但当 Flink 作业正运行在严重的背压下时&#xff0c;Checkpoint 端到端延迟的主要影响因子将会是传递 Checkpoint Barrier 到 所有的算子/子任务的时间&…

乡村振兴与农村基础设施建设:加大农村基础设施建设投入,提升农村公共服务水平,改善农民生产生活条件,构建宜居宜业的美丽乡村

一、引言 乡村振兴是我国现代化进程中的重要战略&#xff0c;而农村基础设施建设则是乡村振兴的基石。随着城市化进程的加快&#xff0c;农村基础设施建设滞后的问题日益凸显&#xff0c;成为制约乡村发展的瓶颈。因此&#xff0c;加大农村基础设施建设投入&#xff0c;提升农…

Docker需要代理下载镜像

systemctl status docker查看docker的状态和配置文件是/usr/lib/systemd/system/docker.service vi /usr/lib/systemd/system/docker.service&#xff0c; 增加如下配置项 [Service] Environment"HTTP_PROXYhttp://proxy.example.com:8080" "HTTPS_PROXYhttp:…

SpringBoot基于微信小程序的星座配对(源码)

博主介绍&#xff1a;✌程序员徐师兄、10年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447…

Springboot+logback 详细配置

一、添加依赖 这里使用springboot3.0.2 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency><dependency><groupId>org.projectlombok</grou…

Java面试之分布式篇

分布式锁的实现方案 &#xff08;1&#xff09;用数据库实现分布式锁比较简单&#xff0c;就是创建一张锁表&#xff0c;数据库对字段作唯一性约束。加锁的时候&#xff0c;在锁表中增加一条记录即可&#xff1b;释放锁的时候删除锁记录就行。如果有并发请求同时提交到数据库&…

springboot2.x集成Elasticsearch7.7.0

一、前言 elasticsearch安装就不做过多介绍了&#xff0c;网上一搜一大堆&#xff1b;最需要注意的就是Elasticsearch与spring版本&#xff0c;防止版本不兼容导致的后续的一系列问题。我这里springbootspring-data-elasticsearch&#xff0c;他们的版本对照关系可以参照sprin…

手写Windows文件路径获取小工具

手写Windows文件路径获取小工具 目的 给Windows右键增加功能&#xff0c;右键任何文件&#xff08;夹&#xff09;显示复制文件路径的扩展。 效果展示 实现思路 右键调用&#xff0c;自身会把文件路径传递给被调用文件&#xff0c;被调用文件内只需将路径参数复制到剪贴板即…

【江科大STM32学习笔记】新建工程

1.建立工程文件夹&#xff0c;Keil中新建工程&#xff0c;选择型号 2.工程文件夹里建立Start、Library、User等文件夹&#xff0c;复制固件库里面的文件到工程文件夹 为添加工程文件准备&#xff0c;建文件夹是因为文件比较多需要分类管理&#xff0c;需要用到的文件一定要复…

day09-常用API异常

1.时间日期类 1.1 Date类&#xff08;应用&#xff09; 计算机中时间原点 1970年1月1日 00:00:00 时间换算单位 1秒 1000毫秒 Date类概述 Date 代表了一个特定的时间&#xff0c;精确到毫秒 Date类构造方法 方法名说明public Date()分配一个 Date对象&#xff0c;并初始化…

【动态规划】子序列问题I|最长递增子序列|摆动序列|最长递增子序列的个数|最长数对链

一、最长递增子序列 300. 最长递增子序列 算法原理&#xff1a; &#x1f4a1;细节&#xff1a; 1.注意子序列和子数组的区别&#xff1a; (1)子序列&#xff1a;要求顺序是固定的&#xff08;要求没那么高&#xff0c;所以子序列就多一些&#xff09; (2)子数组&#xff1a;要…

Python从0到POC编写-魔法方法

name __name__ 是系统定义的内部函数&#xff0c; 它的作用是识别模块。 通常我们看到这样一句话&#xff1a; if __name__ __main____name__ 的值有两种情况&#xff0c;那么挨个来说下。 如果模块是被直接执行的 &#xff0c;那么 __name__ 的值 为 __main__ 例如&…