简体   繁体   English

THREE.js中基于画布的纹理上的透明绘图

[英]Transparent drawing on canvas-sourced texture in THREE.js

I'm creating 2d surfaces in my THREE.js app by creating PlaneGeometry / BasicMaterial meshes and backing their texture with a canvas: 我在THREE.js应用程序中通过创建PlaneGeometry / BasicMaterial网格并使用画布支持其纹理来创建2d曲面:

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
}));

This works fine - unless I want to draw transparently. 正常工作-除非我想透明绘制。 Then, I need to create another texture to bind as alphaMap , and duplicate all my drawing operations between the two canvas contexts. 然后,我需要创建另一个纹理作为alphaMap进行绑定,并在两个画布上下文之间复制所有绘制操作。 The performance is fine, but the code looks absolutely horrendous: 性能很好,但是代码看起来绝对可怕:

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;

I've been planning on writing a small utility library to handle this automatically, but before I did I was wondering if maybe I'm just being silly and there's an easier way to do this. 我一直在计划编写一个小的实用程序库来自动处理此问题,但是在我这样做之前,我想知道是否我只是愚蠢的人,并且有一种更简单的方法可以做到这一点。

You could write a split function instead of rendering to two canvases. 您可以编写一个split函数,而不是渲染到两个画布。 This approach will allow you to issue multiple draw operations to one canvas using alpha channel as intended. 这种方法将允许您按预期使用Alpha通道对一个画布发出多个绘制操作。

When done, just run it through the splitter which returns two canvases, one for color and one gray-scale that can be used for alpha channel. 完成后,只需在拆分器上运行它即可,该拆分器将返回两个画布,一个画布用于颜色,一个灰度用于Alpha通道。

(You could of course reuse the main for color, just note the alpha channel would be gone). (您当然可以将主色用于颜色,只要注意alpha通道将消失)。

Live Splitter Example: 实时拆分器示例:

 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} 
 <canvas></canvas> 

I ended up writing a proxy context that splits the drawing operations between the two canvases. 我最终编写了一个代理上下文,该上下文在两个画布之间拆分了绘图操作。 Needs the csscolorparser` node module (it's on NPM). 需要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]);
}

The code probably has a couple bugs, I haven't run it through any rigorous tests yet, but it worked for the simple bits. 该代码可能有几个错误,我还没有通过任何严格的测试来运行它,但是它只适用于简单的代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM