蓝紫™ v1.4.3
DDZEB 完全自主知识产权图形图像开发平台
在线运行 实时渲染
2024-06-06
二维函数渲染库 curve.js 发布,内置常用的 OpenGL 函数,比如 smoothstep 等,我们可以使用这个库很方便的设计各种函数曲线。示例程序请参考:Wave Fading
2024-03-16
基于 GLSL 语言的渲染引擎 canvasfs 增加对模版函数的支持,详情请参考:蓝紫 GLSL 模版函数。这是一项非常重要也非常实用的技术,我们在标准库中实现了许多模版函数,大大提高了编码效率。
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 的继任技术,主要的优势在于其计算管线、缓冲的控制以及可以对输出纹理的随机写入。但另一方面,WGSL 真的很糟糕,以我们的经验,如果可以使用 GLSL,就尽量别碰 WGSL。

蓝紫的 canvascs 渲染引擎与 canvasfs 引擎同样属于二维渲染范式,也就是对平面上的点进行着色。canvascs 整体上是采用双缓冲架构,一个是当前屏幕纹理(只读),一个是渲染输出纹理(只写)。当然,我们进行渲染的时候也可以不用考虑当前屏幕数据,直接计算出颜色写入输出缓冲即可。

一、JS 接口
const canvas = await Lan.canvascs(1800, 1200, {
  preludes : 'base,cmpx',
  replaces : {from:'to'},
  initfill : new Float32Array(...),
  displays : '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(loop = {})

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

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]));
二、计算管线

我们的所有渲染工作都是通过计算管线完成的,编写计算管线程序就是我们的开发重点。为此,蓝紫提供了强有力的基础,包括全局变量以及尽量简化的计算函数语法等。

2.1、输入输出纹理
// input and output textures
@binding var TI : texture_2d<f32>;
@binding var TO : texture_storage_2d<rgba32float, write>;

其中,TO 为只写的输出纹理,我们计算出颜色数据之后,都要输出到这里。而 TI 则是只读的输入纹理,即:上一帧渲染得到的数据。通常我们不会直接跟它们打交道,而是通过专用的输入输出函数(参考本文第 2.5 节)。

2.2、uniform 参数

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

2.3、全局常量变量2.4、全局函数2.5、类型别名2.6、@run 函数

我们可以编写多个 @run 计算函数,它们会按照声明顺序执行。一个 @run 函数实际上就是执行一个二维计算

@run(RUN_WIDTH, RUN_HEIGHT) {
  // do something ...
}

其中,(RUN_WIDTH, RUN_HEIGHT) 规定二维平面区域大小,如果只提供了 RUN_WIDTHRUN_HEIGHT 默认为 1,如果两者都忽略则采用画布宽高。比如:

@run {
  // do something ...
}

这样就可以在整个画布范围内进行计算,我们可以通过全局变量 COORDUV 等获取当前计算坐标,然后进行计算后将结果写入到输出纹理。WebGPU 允许我们写入到输出纹理的任意位置,而不一定必须写入当前计算点,这是与 canvasfs 引擎巨大的区别所在。计算函数的调度通常是每一帧都按顺序调度一次,除非声明了 @interval

@run @interval {
  // do something
}

对于某些周期性执行的函数可以添加上 @interval 标识,该函数就会按照 options.interval 指定的屏幕输出周期执行。当然,也可以重载这个数字:

@run @interval(100) {
  // do something
}

这时候,@run 函数就会按照每 100 帧执行一次的频率进行调度。另外还有一个特殊的 @interval

@run @interval(once) {
  // do something
}

我们使用 once 来告诉渲染引擎 @run 函数只调用一次。示例可以参考 图像处理计算 程序。

最后一个关于 @run 函数的事情是,您可以为每一段 @run 函数提供一个注释性质的名字或描述:

@run update particles {
  // do something
}

示例参见 粒子系统

三、存储缓冲

在 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}
五、调色板

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

六、后期处理

options.displays 参数里设置后期处理选项,目前支持的操作如下:

七、屏幕输出周期

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

八、字符替换

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

{
  replaces : {NUMBER1:1000},
}

通过这样的设定,WGSL 代码中所有的 NUMBER1 都将被替换为 1000。使用例子请参考向导程序:二维 Mandelbrot 集合

九、自定义 uniforms

通过字符替换,我们可以将 JS 中的值“代入”到 Shader 代码中,这样的好处是快速,Shader 会将那些代入的值作为常量。但这样就无法在程序运行过程中将 JS 中计算得到的数据传入 Shader 程序中。我们提供的 uniforms 就可以解决这类问题。通过在 options 声明:

{
  uniforms : { UniformName : InitialValue, ... },
}

这样我们就可以在 Shader 程序中使用 UniformName 了。要在 JS 中更新,只需要这样:

canvas.UniformName(new_value);

详细使用例子,请参考向导程序:光线追踪。需要注意的是,目前只提供 f32 类型的 uniform 变量支持。