蓝紫™
DDZEB 完全自主知识产权图形图像开发平台
在线运行实时渲染
2024-01-24
基于 GPU 的渲染引擎 canvasfs 以及 canvascs 增加自动后期处理选项,目前包括:自动白平衡、自动对比度、自动饱和度以及色彩增强等 基于全局统计 的后期处理操作。
2023-12-30
调色板支持上线,内置资源提供大量调色板供使用。所有的编程模型均支持 colormap 资源调用,包括 WebGL(canvasfs)、WebGPU(canvascs)以及 Pixel Shader(canvasps),具体使用方法及示例代码请参考相应的 API 文档。
2023-12-17
JS 接口提供音频加载(播放)功能,内置资源增加了少量音频资源。动画配合上音频,会产生更加强烈的现场感。
2023-12-09
提供 Cubemap 以及 HDRI 纹理,canvasfs 附加库提供相应函数支持。样例 Demo 程序 HDRI Mapping 有使用参考。
2023-09-18
蓝紫 WebGPU 渲染引擎 canvascs 正式发布上线。有了 WebGPU 渲染支持,就可以很直接的实现 Buddhabrot、Flame 等迭代渲染,也可以非常方便的编写粒子系统以及流体模拟等动画渲染程序。
蓝紫™ - 开发接口

WebGPU 是 WebGL 的继任技术,主要的优势在于其计算管线。蓝紫 canvascs 的编程模型与 canvasfs 都是属于二维的渲染模型,但也都能处理三维的情况。相比之下,canvascs 更加强大简洁,可以适用的场景范围也更加广泛。

蓝紫的 WebGPU 渲染引擎整体上是采用双缓冲架构,一个是当前屏幕纹理(只读),一个是渲染输出纹理(只写)。当然,我们进行渲染的时候也可以不用考虑当前屏幕数据,直接计算出颜色写入输出缓冲即可。

一、JS 接口
const canvas = await Lan.canvascs(1800, 1200, {
  preludes : 'base,cmpx',
  replaces : {from:'to'},
  initfill : new Float32Array(...),
  displays : 'rgbc,gw,rm,ls,cs,ss,ce',
  interval : 0,
  storages : [100 * 200 * 4, new Uint32Array([10, 20]), ...],
  textures : ['url', {source:'url', flipy:true}, ...],
  texflipy : true,
  colormap : 'M1,M2'
});
await Lan.loop(canvas.render);

我们可以通过 preludes 加载扩展库,蓝紫内置了几个常用的扩展库,用于一些特定的场景。比如,base 提供一些常用操作,cmpx 提供二维复数操作。加载扩展库可以有如下几种方式:

preludes : 'base,pale,/lib/url'
preludes : ['base,pale', '/lib/url']

以下是蓝紫内置的 WGSL 扩展库,可直接通过名称导入,名称不区分大小写:

initfill 用于初始化画布数据,指定一个 Float32Array,每 4 个元素代表一个像素的 RGBA 分量。其它参数请参考本文档后续部分。

1.1、async canvas.render(ts = 0, ds = 0, fm = 0, dt = 0)

该接口为渲染接口,通常我们会调用 Lan.loop(canvas.render) 来进行持续的动画渲染,当然,我们也可以直接调用 canvas.render 来渲染某一帧画面。参数与 Lan.loop 需要的参数一致:

1.2、async canvas.storage

使用该接口可以读取或更新存储缓冲,读取的时候调用方式为:

const index = 0;
canvas.storage(index, (data) => {
  // data is of type Uint32Array
  // do anything with data ...
  // return true if you want to write data back to GPU
});

当回调函数返回 true 的时候,data 会被写回 GPU 中从而更新缓冲数据。如果我们想直接更新数据,也可以通过以下更加高效的方式更新 storage 的内容:

const index = 0;
const storageOffset = 10;
canvas.storage(index, new Uint32Array([123]), storageOffset);
canvas.storage(index, new Uint32Array([456]));
二、计算管线

我们可以编写多段 $CS 计算管线程序,所有程序代码会整合(concat)为一个,并在最前面添加如下代码:

// constants
const dimx = $canvas_width;
const dimy = $canvas_height;
const dimi = vec2i(dimx, dimy);
const dimu = vec2u(dimx, dimy);
const dimf = vec2f(dimx, dimy);
const dimc = vec2f($canvas_width / 2, $canvas_height / 2);
const aspect = $canvas_width / $canvas_height;

// uniform
struct UNIFORM {time:f32, timeDelta:f32, frame:u32, mouse:vec3f, mouse1:vec2f};
@group(0) @binding(0) var UF:UNIFORM;

// input and output textures
@group(0) @binding(1) var TI:texture_2d;
@group(0) @binding(2) var TO:texture_storage_2d;
2.1、uniform 参数

我们可以通过 UF 访问所有的 uniform 参数:

关于鼠标交互,可以参考官方 Demo:WebGPU Mouse Interaction

2.2、@compute 函数

在计算管线程序中,我们可以编写多个 @compute 计算函数,它们会按照声明顺序执行。通常,我们会在最后一个 @compute 计算函数里更新输出缓冲。一个典型的计算函数如下:

@compute @workgroup_size(16, 16) @dispatch(100, 100) @interval
fn mainCS(@builtin(global_invocation_id) id:vec3u) {
  if (id.x >= dimx || id.y >= dimy) { return; }
  rseed(id); // initialize random seed

  // coordinates, x in [-1, +1] and y follows the aspect ratio
  let uv = coord(id);
  
  // render
  var col = render(uv);

  // post processing ...

  // output
  outstore3(id, col);
}

其中 @interval 表示该函数执行周期与屏幕输出周期一致,@dispatch(X, Y) 指定分派的计算组数量,其中 X 和 Y 可以为小数。@workgroup_size 的默认值为 (1, 1),而 @dispatch 默认为 ceil(canvas_dimensions / workgroup_size)。蓝紫可以自动补全缺省的声明,所以,我们可以将一个计算函数简单的声明为如下形式:

@compute mainCS(id) {
  rseed(id);
  ......
}

因为在缺省情况下,id 的 xy 坐标与画布的像素坐标一一对应,我们不用再检查 id 坐标是否越界。对于某些周期性执行的函数可以添加上 @interval 标识,该函数就会按照 options.interval 指定的屏幕输出周期执行:

@compute @interval mainCS(id) {
    rseed(id);
    ......
  }

关于 @interval 的使用,可以参考 WebGPU Buddhabrot 程序。

三、存储缓冲

在 options 里通过 storages 指定存储缓冲,可以指定一个,也可以是多个:

{
  // 一个存储缓冲,大小为 4 个 u32,即 16 字节
  storages : 4,
}

{
  // 两个存储缓冲,一个 16 字节,第二个 40 字节
  storages : [4, 10],
}

{
  // 一个存储缓冲,两个 u32 数据,直接上传到 GPU
  storages : new Uint32Array([1e20, 0]),
}
  

整体来说,存储缓冲是一个 u32 数组,u32 可以通过 WGSL 的 bitcast 函数转换为 i32f32 等类型。所有的缓冲都会被声明为 array<atomic<u32>> 类型,并被命名为 storagex,其中 x 为序号。

let index = 32u;
atomicAdd(&storage0[index], 1);

let val:f32 = 1.41;
atomicStore(&storage0[index], bitcast<u32>(val));
  

此外,我们还可以通过 storagexLen 常量来取得存储缓冲的长度。

四、纹理资源

加载纹理与 canvasfs 引擎类似,需要在 JS 接口的 textures 数组指定纹理 url 或者一个 HTMLCanvasElement 对象作为纹理源。我们可以将音频、视频以及 Cubemap 资源加载为纹理,所有纹理加载进来后都是 texture_2d_array<f32> 结构。音频 FFT 频率分布数据会被转换为一个高度为 1 的二维图像进入程序,Cubemap 是一个 6 层的 texture_2d_array 数组。纹理命名与缓冲命名类似,texturex,其中 x 为序号。

var c = textureLoad(texture0, vec2u(id.x, id.y), 0, 0).rgb;

因为 textureLoad 在采样的时候没有使用任何 filter,我们在 base 附加库中提供了几个采样函数,可以方便我们进行二维纹理采用以及 HDRI、Cubemap 采样。

var c0 = texture(texture0, uv);
var c1 = texHdri(texture1, dir);
var c2 = texCube(texture2, dir);

其中,缺省采用 cubic B-spline 采样,如果要修改采样算法,请使用带 Filter 的函数。它们的具体使用方法请参考示例程序:WebGPU HDRI 以及 WebGPU Cubemap

除了加载常规的图片、视频等纹理外,当 source 设置为 'camera' 时,我们会试着打开设备的照相机:

textures : 'camera'
textures : {source:'camera'}

我们还可以将一个 HTMLCanvasElement 作为纹理源,当 live 为 true 时,会实时更新,具体情况请参考示例程序:Canvas Texture

textures : {source:canvas_element, live:true}
五、渐进渲染

这里所说的渐进渲染,特指颜色累加后平均的情况。我们使用 alpha 通道(color.w)来存储累加次数,而 color.rgb 是实际累加的颜色。在显示的时候渲染的颜色为 vec4f(color.rgb / color.w, 1),alpha 通道恒为 1。

在我们的示例程序里,有一个渲染 Julia 集合的例子,使用的就是 WebGPU 进行渐进渲染反走样输出。这时候,我们需要将 displays 设置为 rgbc,意思是第四个通道代表计数(count)。

六、调色板

蓝紫内置资源中有调色板支持,我们可以在 options 中指定调色板(colormap),然后通过 base 附加库中的 colormap 函数获取颜色。目前 canvascs 最多可以同时加载 4 个调色板,使用代码请参考官方 Demo:WebGPU Colormaps

七、后期处理

除了在 displays 参数里设置 rgbc 渐进渲染模式以外,还可以指定后期处理选项,目前支持的操作如下:

八、屏幕输出周期

有些时候我们希望程序可以尽可能快的进行计算和渲染,间隔一段时间后输出到屏幕。我们可以指定 options.interval 来设置输出到屏幕的帧周期。比如,设置为 100,那么就会每隔 100 帧输出到屏幕。

九、字符替换

options 里指定 replaces 项,可在 WGSL 代码中进行字符串替换操作。

{
  replaces : {NUMBER1:1000},
}

通过这样的设定,WGSL 代码中所有的 NUMBER1 都将被替换为 1000