【问题标题】:Transparent drawing on canvas-sourced texture in THREE.jsTHREE.js 中画布来源纹理上的透明绘图
【发布时间】:2015-06-29 09:39:54
【问题描述】:

我正在我的 THREE.js 应用程序中创建二维表面,方法是创建 PlaneGeometry/BasicMaterial 网格并用画布支持它们的纹理:

this.canvas = makeCanvas(canvasWidth, canvasHeight);
this.ctx = this.canvas.getContext('2d');

this.texture = new THREE.Texture(this.canvas);
this.texture.minFilter = THREE.LinearFilter;
this.texture.magFilter = THREE.LinearFilter;
this.texture.anisotropy = 16;

this.mesh = new THREE.Mesh(new THREE.PlaneGeometry(width, height), new THREE.MeshBasicMaterial({
    map: this.texture,
    overdraw: true,
    side: THREE.DoubleSide,
    transparent: true
}));

这很好用——除非我想透明地绘制。然后,我需要创建另一个纹理以绑定为alphaMap,并在两个画布上下文之间复制我的所有绘图操作。性能很好,但代码看起来绝对可怕:

var circleAlpha = 'rgb(100, 100, 100);',
    segmentAlpha = 'rgb(200, 200, 200);';
ctx.fillStyle = 'rgb(0, 255, 255);';
if (this.segment === -1) {
    circleAlpha = segmentAlpha;
}
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.alphaCtx.clearRect(0, 0, this.canvas.width, this.canvas.height);
var drawArc = function (c, w, h, piece) {
    var angw = 2 * Math.PI / 6;
    c.beginPath();
    c.arc(w / 2, h / 2, 512, angw * piece, angw * (piece + 1), false);
    c.arc(w / 2, h / 2, 300, angw * (piece + 1), angw * piece, true);
    c.closePath();
    c.fill();
};
for (var i = 0; i < 6; i++) {
    this.alphaCtx.fillStyle = i == this.segment ? segmentAlpha : circleAlpha;
    drawArc(ctx, this.canvas.width, this.canvas.height, i);
    drawArc(this.alphaCtx, this.canvas.width, this.canvas.height, i);
}
this.updateTexture();
this.alphaTexture.needsUpdate = true;

我一直在计划编写一个小型实用程序库来自动处理这个问题,但在我这样做之前,我想知道我是不是很傻,有一种更简单的方法可以做到这一点。

【问题讨论】:

    标签: javascript canvas opengl-es three.js html5-canvas


    【解决方案1】:

    我最终编写了一个代理上下文,将两个画布之间的绘图操作分开。需要 csscolorparser 节点模块(在 NPM 上)。

    var FakeCtx = function (canvasA, canvasB) {
        this.ctxA = canvasA.getContext('2d');
        this.ctxB = canvasB.getContext('2d');
    
        this.colorStore = {};
    };
    
    var assignContextProperty = function (property, propertyType) {
        if (propertyType === 'function') {
            FakeCtx.prototype[property] = function () {
                var argArray = Array.prototype.slice.call(arguments),
                    ra = this.ctxA[property].apply(this.ctxA, argArray),
                    rb = this.ctxB[property].apply(this.ctxB, argArray);
                if (ra !== rb) {
                    var argString = argArray.join(', ');
                    debug.warn('Console proxy got two different results for calling ' + property + ':');
                    debug.warn('    Canvas A: ' + property + '(' + argString + ') = ' + ra);
                    debug.warn('    Canvas B: ' + property + '(' + argString + ') = ' + rb);
                }
                return ra;
            };
        } else {
            if (property != 'fillStyle' && property != 'strokeStyle') {
                FakeCtx.prototype.__defineGetter__(property, function () {
                    return this.ctxA[property];
                });
                FakeCtx.prototype.__defineSetter__(property, function (value) {
                    this.ctxA[property] = this.ctxB[property] = value;
                });
            } else {
                FakeCtx.prototype.__defineGetter__(property, function () {
                    return this.colorStore[property] || this.ctxA[property];
                });
                FakeCtx.prototype.__defineSetter__(property, function (value) {
                    var color = csscolor.parseCSSColor(value);
                    if (color === null || color.length < 3) {
                        throw new Error('Invalid color ' + value + ': ' + color);
                    } else {
                        this.colorStore[property] = value;
                        if (color.length === 3 || color[3] === 1) { // no alpha
                            this.ctxA[property] = value;
                            this.ctxB[property] = 'rgb(255, 255, 255)'; // white = full alpha
                        } else {
                            this.ctxA[property] = 'rgb(' + color.slice(0, 3).join(', ') + ')';
                            var alphaValue = Math.round(255 * color[3]);
                            this.ctxB[property] = 'rgb(' + [ alphaValue, alphaValue, alphaValue ].join(', ') + ')';
                            // console.log('got color with alpha ' + value + ', splitting to ' + 'rgb(' + color.slice(0, 3).join(', ') + ');' + ' and ' + 'rgb(' + [ alphaValue, alphaValue, alphaValue ].join(', ') + ');');
                        }
                    }
                });
            }
        }
    }
    
    var _ctx = makeCanvas(0, 0).getContext('2d');
    for (var property in _ctx) {
        assignContextProperty(property, typeof _ctx[property]);
    }
    

    代码可能有几个错误,我还没有通过任何严格的测试来运行它,但它适用于简单的部分。

    【讨论】:

      【解决方案2】:

      您可以编写一个拆分函数,而不是渲染到两个画布。这种方法将允许您按预期使用 Alpha 通道向一个画布发出多个绘制操作。

      完成后,只需通过拆分器运行它,它会返回两个画布,一个用于颜色,一个用于 Alpha 通道的灰度。

      (您当然可以将 main 用于颜色,只需注意 alpha 通道将消失)。

      实时拆分器示例:

      var ctx = document.querySelector("canvas").getContext("2d"),
          gr = ctx.createLinearGradient(0, 0, 300, 0);
      
      // draw something with alpha channel to main canvas
      gr.addColorStop(0, "rgba(255,140,0,1)");
      gr.addColorStop(1, "rgba(255,0,0,0)");
      ctx.fillStyle = gr;
      ctx.fillRect(0, 0, 300, 150);
      
      // split the canvas to two new canvas, one for color, one for alpha
      var maps = splitTexture(ctx);
      
      document.body.appendChild(maps.color);  // show in DOM for demo
      document.body.appendChild(maps.alpha);
      
      // Split texture:
      function splitTexture(ctx) {
      
        var w = ctx.canvas.width,
            h = ctx.canvas.height,
            canvasColor = document.createElement("canvas"),
            canvasAlpha = document.createElement("canvas"),
            ctxc = canvasColor.getContext("2d"),
            ctxa = canvasAlpha.getContext("2d"),
            
            idata = ctx.getImageData(0, 0, w, h),
            data32 = new Uint32Array(idata.data.buffer),  // use uint-32 buffer (faster!)
            len = data32.length, i = 0, p, a, g,
            adata, adata32, cdata, cdata32;               // destinations
      
        canvasColor.width = canvasAlpha.width = w;        // set same size as source
        canvasColor.height = canvasAlpha.height = h;
      
        cdata = ctxc.createImageData(w, h);               // create buffers and uint32 views
        cdata32 = new Uint32Array(cdata.data.buffer);
      
        adata = ctxa.createImageData(w, h);
        adata32 = new Uint32Array(adata.data.buffer);
        
        // splitter loop
        while(i < len) {
          p = data32[i];                                        // source pixel as 32-bit ABGR
          a = p & 0xff000000;                                   // mask out alpha
          g = 0xff000000 | (a >>> 8) | (a >>> 16) | (a >>> 24); // grey value
          adata32[i] = g;                                       // set gray value
          cdata32[i++] = 0xff000000 | (p & 0xffffff);           // set color value
        }
        
        ctxc.putImageData(cdata, 0, 0);                         // update destinations
        ctxa.putImageData(adata, 0, 0);
        
        return {
          color: canvasColor,
          alpha: canvasAlpha
        }
      }
      body {background:#79c}
      &lt;canvas&gt;&lt;/canvas&gt;

      【讨论】:

      • 感谢您的代码。我最终做了类似的事情,但是在上下文 API 级别上进行拆分,以避免中间图像缓冲区。
      猜你喜欢
      • 2021-09-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-25
      • 2017-09-09
      • 2021-08-30
      • 2013-04-23
      相关资源
      最近更新 更多