Bulletproof 和 GLantern 更新:抗锯齿与非正常变换

GLantern 的绘制方式更新了!获得了技能:抗锯齿(一般绘制)、绘制效率提高、抗溢出变换。Bulletproof 作为直接继承项目,也得到了特性的更新。

另外,GLantern 的 live preview 也上线了(呃,之前忘了)。请戳:Bulletproof/GLantern

首先说一下原来是怎么画的:

DisplayObject.prototype.render = function (renderer, output) {
    this.__render(renderer, this._rawTarget);
    this.__processFilters(renderer, this._rawTarget, this._bufferedTarget);
    RenderHelper.copyTargetContent(renderer, this._bufferedTarget, output);
};

DisplayObjectContainer.prototype.render = function (renderer, output) {
    super.render(renderer, this._containerTarget);
    for (var i = 0; i < this._children.length; ++i) {
        this._children[i].render(renderer, this._containerTarget);
    }
    RenderHelper.copyTargetContent(renderer, this._containerTarget, output);
};

这导致了什么问题呢?

  1. 大量借助 WebGLFramebuffer 的拷贝(通过 shader 拷贝),每个元素绘制都会有1到2次拷贝操作。
  2. WebGLFramebuffer 是没有抗锯齿的,所以所有的图元绘制都带有锯齿。
  3. 一个潜在的问题:假设舞台大小为 100×100,元素A大小为 50×50,位于位置 (-200, -200) 处,其逻辑父B位于位置 (200, 200) 处,则A就画不出来了。

对于问题3的稍详细的解释:因为第一次变换的时候边界 (-200,-200,-150,-150) 就已经超出了视场 (0,0,100,100) 的范围,所以做 visiblility test 的时候会失败而不会绘制;但是,稍微计算一下的话就会发现A的实际位置在 (0,0,50,50),应该被画出来。这是一个刁钻的情况,但是可以发生。

现在的绘制流程就简单多了,直接向屏幕(相当于 gl.bindBuffer(gl.FRAMEBUFFER, null))绘制。对原先的 render() 方法,干脆去掉了 outputTarget 这个冗余的参数,因为 WebGLRenderer 已经有了一个公开属性 currentTarget,同时出现两套同样意义的字段是不合适的。抽象调用:

render(renderer:WebGLRenderer):void {
    if (this.visible && this.alpha > 0) {
        this.__preprocess(renderer);
        this.__render(renderer);
        this.__postprocess(renderer);
    }
}

具体实现(这里取进行了最底层绘制操作的 SolidStrokeRenderer 为例子):

render(renderer:WebGLRenderer):void {
    if (this._vertices.length > 0) {
        var target = renderer.currentRenderTarget;
        RenderHelper.renderPrimitives2(renderer, target, this._vertexBuffer, this._colorBuffer, this._indexBuffer, false, target.isRoot, false);
    }
}

虽然在 WebGL 1.0 中,FBO(frame buffer object)不支持抗锯齿,但是屏幕渲染器是支持的。对于直接绘制到屏幕上的元素(例如不带滤镜的 Shape)就可以用上抗锯齿功能了。同时,拷贝操作的大量减少提高了绘制效率,现在小圆脸即使不缓存 Graphics 结果也可以稳定在60帧了(测试基准:NW.js v0.12.3 stable)。

请注意,这里为了能直接将最终位置反映到屏幕上,新建了一个 shader:Primitive2Shader。在片元部分和 PrimitiveShader 是一样的,简单颜色赋值而已。但是在顶点处理上就复杂一点了:

precision mediump float;

attribute vec3 aVertexPosition;
attribute vec4 aVertexColor;

uniform mat4 uProjectionMatrix;
uniform mat4 uTransformMatrix;
uniform vec2 uOriginalSize;
uniform vec2 uFitSize;
uniform bool uFlipX;
uniform bool uFlipY;

varying vec4 vVertexColor;

void main() {
    vec3 newVertexPostion = aVertexPosition;
    if (uFlipX) {
        newVertexPostion.x = uOriginalSize.x - newVertexPostion.x;
    }
    if (uFlipY) {
        newVertexPostion.y = uOriginalSize.y - newVertexPostion.y;
    }
    gl_Position = uProjectionMatrix * uTransformMatrix * vec4(newVertexPostion.xyz, 1.0);
    vVertexColor = aVertexColor;
}

第一步是要支持 uTransformMatrix 的计算,这个计算由 DisplayObject.__updateTransform() 完成,在必要的时候触发计算。最终的值会被传入 shader。

第二步就是其他处理。有没有想过,为什么会出现 X 轴和 Y 轴的翻转?答案是,OpenGL(数学)坐标系和 Flash(GDI)坐标系关于 Y 轴是相反的。为了正确地一次画出图形,需要在 shader 中立即执行 Y 轴翻转。而翻转的轴是 (0,H)(H 是图元高度),因此还需要一个额外参数,图元大小(uOriginalSize),指示翻转轴位置。

解决了这些之后,就是代码兼容性调整了,耐心+细心将编译错误和运行错误解决了,三个测试样例通过,收工。

分享到 评论