APIs - 蓝紫开发接口

这是蓝紫非常重要的一种渲染画布,它满足如下渲染范式:

color(p) = f(px, py)

也就是说,我们的渲染程序需要根据画布上每一个点的坐标给出颜色。这样的渲染模型看起来就是一个二维渲染范式,但实际上,我们可以很容易的实现三维渲染,前提是我们手动放置相机以及处理投射关系。

canvasfs 使用 WebGL 2.0 Fragment Shader 进行渲染,

一、JS 接口

canvasfs 的程序由两部分组成,首先是一段 $JS 程序作为主控程序,另外就是运行在 GPU 上的 $FS(Fragment Shader)程序。JS 主控程序负责初始化画布,并调取可能使用到的纹理等等资源。

const canvas = await Lan.canvasfs(width, height, options);

一个典型的 JS 主控程序如下:

const canvas = await Lan.canvasfs(1800, 1200, {
  preludes : 'base,cmpx',
  replaces : {from:'to'},
  displays : 'gw,rm,ls,cs,ss,ce',
  textures : ['T02', {source:'T03', mipmap:true, flipy:false}],
  texflipy : true,
  colormap : 'M1,M2'
});
await Lan.loop(canvas.render);

初始化画布之后,除了常规的画布接口,我们得到的 canvas 对象还具有如下几个额外接口:

1.1、canvas.render(loop = {})

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

1.2、canvas.run[x]

属性。我们可以通过 run[x] 访问 @run 函数,比如:canvas.run0 访问第 0@run 函数。返回的对象有如下属性或方法:

使用示例参见 Gaussian Kernel

二、Fragment Shader

编写 $FS 程序,即 Fragment Shader,实现渲染。

2.1、@run 函数

$FS 程序里可以编写一个或多个按照顺序执行的 @run 函数,编号从 0 开始,它们是 Fragment Shader 的渲染函数。每一个 @run 函数渲染结束后都会将数据写入到对应的输出纹理(outcome),比如,第 0@run 函数输出到 outcome0。屏幕(画布)上显示的结果是最后一个被执行的 @run 函数的输出。

@run 函数基本形式如下:

@run {
  vec3 color = vec3(0);
  // do some calculations to get a correct color
  FLUSH(color);
}

大概意思是,在 Fragment Shader 的 @run 函数里做一些计算,然后输出颜色。我们还可以使用 @active 来决定其是否活跃,以及给 @run 提供一个注释性质的名字或描述:

@run @active(false) post process {
  // code ...
}

默认的 @active 值为 true,表示正常运行该函数。此外,当我们需要极致渲染性能的时候,可以使用 @target 告诉渲染引擎此 @run 函数的渲染目标:

@run @target(screen) this would be the LAST rendering action {
  // code ...
}

渲染目标的可能取值有如下三个:

2.2、uniform 参数2.3、全局常量变量2.4、全局函数三、扩展库

我们可以通过 preludes 加载扩展库,蓝紫内置了几个常用的扩展库,用于一些特定的场景。比如,base 提供一些常用操作,cmpx 提供二维复数操作。某些扩展库里有模版函数,相关情况请参考博客:蓝紫 GLSL 模版函数。加载扩展库可以有如下几种方式:

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

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

四、纹理加载

通过在 options 参数里指定 textures 可以加载纹理,可以有如下几种方式:

textures : 'T01,T02'
textures : {source:'V01', mute:false, wrap:'mirrored'}
textures : ['T01', {source:'T02', mipmap:true, blur:8, scale:2}]

其中的 mipmapblur 以及 scale 是专门针对图片纹理的选项,参见向导程序:KIFS 分形。加载 HDRI 或 Cubemap 也是同样的方式:

textures : 'H01,C01'

除了可以将图像、视频加载为纹理外,还可以将音频加载为纹理:

textures : 'A01'
textures : {source:'.....', audio:true, loop:true}

当 source 设置为 'camera' 时,我们会试着打开设备的照相机:

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

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

textures : {source:canvas_element, live:true}

最后,是数据纹理,我们可以使用 Float32Array 作为数据纹理源,设置好宽度(width)或高度(height),不设置尺寸的时候,认为宽度为整个数据长度:

const data = new Float32Array([1,2,3,4,5,6]);
textures : {source:data, width:2}

这样就得到了一个 2x3 大小的数据纹理。关于数据纹理的使用,请参考示例程序:Gaussian Kernel

五、调色板

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

六、后期处理

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

七、字符替换

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

{
  replaces : {NUMBER1:1000},
}

通过这样的设定,GLSL 代码中所有的 NUMBER1 都将被替换为 1000。使用例子请参考向导程序:二维域翘曲渲染

八、自定义 uniforms

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

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

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

canvas.UniformName(new_value);

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

九、缓冲初始化

我们在 $FS 程序中通过 @run 函数进行渲染并输出到自己的缓冲 outcome 中,比如,第 0 个 @run 函数输出到 outcome0。在 @run 函数渲染过程中,有可能会用到之前渲染输出的数据,我们通过 PRIOR 函数可以很方便的取得。但在 @run 函数首次运行的时候,这些数据都是空的,即,RGBA 全部为 0。我们可以通过 options.initfill 来为第 0 个 @run 函数提前准备数据。

initfill : (x, y) => {
  var r, g, b, a;
  // calculate ...
  return [r, g, b, a];
}

具体例子请参见示例程序:Paint Flow