滤镜在 Bulletproof 中的应用,算法选用
滤镜一向是 Bulletproof 的软肋。由于不是采用 SVG 而是 Canvas,所以两个滤镜只好在内部计算完成。
晚上蛋疼地想,之前小圆脸的背景滤镜只计算了一次(在刚刚加载的时候;其余时候内容不变所以不会重算和重绘),如果发光用多了会怎么样?于是我在 filters = true
的条件下运行了三维球的示例。结果大约就 6 fps,和我当年玩镜之边缘相当。(不过我好歹在这个条件下还玩到 The Boat 这一章呢。)
有人可能会问:你不直接给 canvas 加一个效果吗?比如说模糊:
canvas {
-webkit-filter: blur(10px);
-moz-filter: blur(10px);
-o-filter: blur(10px);
filter: blur(10px);
}
建议大家动手试一试。然后你就会发现,这个模糊会将 canvas 以外的所有像素当做黑色,然后进行高斯模糊。在边界上,会向内坍缩出一个黑色的羽化边界。
至于发光呢,这个更坑爹。肯定是用 box-shadow 这个没话说。<canvas>
作为一整个元素进行处理,出来的东西是以 <canvas>
的矩形作为标准计算出来的。
SVG 就好说很多。例如发光(摘自jabbany的代码生成元素):
<svg>
<defs>
<filter id="feglow0" x="-100%" y="-100%" width="400%" height="400%">
<feColorMatrix type="matrix" values="0 0 0 255 0 0 0 0 255 0 0 0 0 0 0 0 0 0 1 0">
</feColorMatrix>
<feGaussianBlur stdDeviation="40 40" result="coloredBlur">
</feGaussianBlur>
<feMerge>
<feMergeNode in="coloredBlur"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
</defs>
<g filter="url(#feglow0)">
<g transform="translate(329.5,169.5)" opacity="1">
<g>
<path d="...">
</path>
</g>
</g>
</g>
</svg>
另外如果是模糊,只需要 <feGaussianBlur>
就可以了。
可以看出,原理是一样的,但是 SVG 能更有效地利用浏览器提供的内置功能——而且是标准化了的功能。所以帧率限制根源是 DOM 元素增删操作,普通的混合处理如果功能内置那么可以达到原生的速度。
Bulletproof 没这么好运,因为 canvas 提供的是像素数组,而不是抽象对象描述。这个行为就和自己计算绘制三角形,比起用顶点索引数据让 DirectX/OpenGL 画三角形,效率自然打了很多折扣。
在这之前,我也说过计算了纯色模糊后我使用 alpha blend 合成图像。嗯,又是大数组操作。但是再想一想,如果你使用 drawImage()
,将一个 <img>
上的带半透明像素的图像绘制到某个有内容的 <canvas>
上面,难道显示就不正常了?所以很明显,alpha blend 有浏览器帮我们算,为何要吭哧吭哧地用 JavaScript 进行运算呢?
于是帧率从 ~6 fps 提高到了 ~13 fps。
能不能更快?谷歌了一下(抱歉)。Mario Klingemann 给出了4个算法,Stack Blur、Superfast Blur、Integral Image Blur 和 StackBox Blur。我之前看到所谓“superfast”,就选了。Mario 好心地给出了 JSPerf 的测试链接,我就试了试,结果让我大吃一惊:StackBox Blur 单次迭代比 Superfast Blur 要快上大概 50%(34 ops/s vs. 24 ops/s)。
当然,StackBox Blur 高效率的副作用是低精度。单次迭代效果看起来并不是那么接近高斯模糊。Mario 自己也写了:
I am not sure which one of my four box blur algorithms is faster. Lets find out. Additionaly there is a comparison to StackBlur, which is an almost gaussian blur but slower, though to be fair one has to apply 2x box blur to get the same smoothness.
不过弹幕里并不要求很高的精度。遂将模糊算法换成了 StackBox Blur。运行一秒稳定后能维持在 15 fps 以上:
不禁慨叹,j君的历史页就给了我成吨伤害,还要再拜天下大神啊。
所以各位高级弹幕的制作者们,请手下留情,不要疯狂地用滤镜。