蓝紫™
DDZEB 完全自主知识产权图形图像开发平台
在线运行 实时渲染 立即开启
基于 WebGL 2.0 的 Fragment Shader 渲染引擎。
文件大小版本开发者最后更新
lands/frag production17.0KBv1.0.0YB2024-10-16查看
lands/frag source code37.8KBv1.0.0YB2024-10-16查看
简要介绍

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

color(p) = f(px, py)

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

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

一、JS 接口

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

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

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

const canvas = await Lan.frag(1800, 1200, {
preludes : 'base,cmpx',
replaces : {from:'to'},
textures : ['T02', {source:'T03', mipmap:true, flipy:false}],
painters : 'P1,P2',
tilesize : 300,
});
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

1.3、canvas.clear([params])

目前该函数仅用于清除输出缓冲数据,比如,清理第 2 个 @run 函数的输出缓冲数据:

canvas.clear({outcome:2});
二、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 ...
}

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

当没有渲染到屏幕的 @run 函数时,我们会将最后一个 @run 函数的输出渲染到屏幕。

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 中指定颜色桶(painters),然后通过 color 附加库中的 painter 函数获取颜色。使用代码请参考官方 Demo:WebGL Painters

六、字符替换

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

{
replaces : {NUMBER1:1000},
}

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

七、自定义 uniforms

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

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

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

canvas.uniforms.UniformName = new_value;

需要注意的是,UniformName 必须以大写字母开头,并且目前只提供 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

九、分块渲染

在渐进渲染静态图像的时候,如果渲染流程特别耗时,我们可以采用分块渲染来提高帧率,不至于让 WebGL 崩溃而无法渲染。分块渲染参数为 tilesize,通过该参数指定分块大小:

tilesize : 300 // 300 x 300
tilesize : '300x200' // 300 x 200
tilesize : '400,1' // 400 x 1

当只有一个数字的时候,我们按照正方形处理。当同时指定宽高的时候,可以使用任意非数字符号分割宽高数字。详细使用例子,请参考向导程序:路径追踪

十、数据精度

缺省情况下,我们都是采用 highp 精度的数据,可以通过在 options 中配置 bitlevel 调整数据精度:

另外,输出纹理的精度,缺省情况下为 32 位精度,缺少 EXT_float_blend 扩展的时候(大部分手机上)为 16 位精度。我们可以通过设置 force16f 将精度强制设置为 16 位。大部分情况下,我们不需要对精度进行调整,精度调整对程序性能的影响不是很显著。