如何在javascript

时间:2016-06-25 04:21:02

标签: javascript html5 canvas

嗨我想制作一个像这样的模糊效果粒子:

particle

我可以使用shadowBlurshadowOffsetX/shadowOffsetY来执行此操作吗?实际的光泽会反复发光和褪色,所以如果我必须编写某种动画,我该如何实现呢?

我已尝试过此代码(jsfiddle example),但它看起来并不像效果。所以我想知道如何同时模糊和发光粒子?



const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const ra = window.requestAnimationFrame
  || window.webkitRequestAnimationFrame
  || window.mozRequestAnimationFrame
  || window.oRequestAnimationFrame
  || window.msRequestAnimationFrame
  || function(callback) {
    window.setTimeout(callback, 1000 / 60);
  };
  
class Particle {
	constructor(options) {
  	this.ctx = options.context;
  	this.x = options.x;
    this.y = options.y;
    this.radius = options.radius;
    this.lightSize = this.radius;
    this.color = options.color;
    this.lightDirection = true;
  }
  
	glow() {
    const lightSpeed = 0.5;

    this.lightSize += this.lightDirection ? lightSpeed : -lightSpeed;

    if (this.lightSize > this.radius || this.lightSize < this.radius) {
      this.lightDirection = !this.lightDirection;
    }
  }
  
	render() {
  	this.ctx.clearRect(0, 0, canvas.width, canvas.height);
    this.glow();
  	this.ctx.globalAlpha = 0.5;
    this.ctx.fillStyle = this.color;
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, this.lightSize,
      0, Math.PI * 2
    );
    this.ctx.fill();
    
    this.ctx.globalAlpha = 0.62;
    this.ctx.beginPath();
    this.ctx.arc(this.x, this.y, this.radius * 0.7, 0, Math.PI * 2);
    this.ctx.shadowColor = this.color;
    this.ctx.shadowBlur = 6;
    this.ctx.shadowOffsetX = 0;
    this.ctx.shadowOffsetY = 0;
    this.ctx.fill();
  }
}

var particle = new Particle({
	context: ctx,
  x: 60,
  y: 80,
  radius: 12,
  color: '#4d88ff'
});

function run() {
	particle.render();
	ra(run);
}

run();
&#13;
<canvas id='canvas'></canvas>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:1)

有几种方法可以做到这一点。对于粒子系统,我的选择是使用模糊滤镜预渲染模糊。常见的滤波器是卷积滤波器。它使用一个小数组来确定相邻像素对图像的每个像素的贡献量。你最好查找卷积函数来理解它。

Wiki ConvolutionWiki Gaussian blur了解详情。

我不太喜欢标准的高斯模糊或使用的卷积滤镜,因此在下面的演示片段中,您可以找到我认为可以创建更好模糊的版本。卷积模糊滤镜是在程序上创建的,位于imageTools对象中。

要创建过滤器,请使用属性为size的对象传递模糊量(以像素为单位),power为强度。较低的权力在模糊上的传播较少。

// image must be loaded or created
var blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
// apply the convolution filter on the image. The returned image may be a new 
//image if the input image does not have a ctx property pointing to a 2d canvas context
image = imageTools.applyConvolutionFilter(image,blurFilter);

在演示中,我创建了一个图像,在其上绘制一个圆圈,复制并填充它,以便有模糊的空间。然后创建一个模糊滤镜并将其应用于图像。

当我渲染粒子时,我首先绘制所有不模糊的图像,然后使用ctx.globalCompositeOperation = "screen";绘制模糊的副本,使它们具有光泽。为了改变光泽,我使用ctx.globalAlpha来改变渲染的模糊图像的强度。为了改善效果,我已经绘制了两次模糊图像,一次是振荡比例,另一次是固定比例和alpha。

演示很简单,可以在顶部找到图像工具。然后有一些东西来设置画布和处理resize事件。然后是创建图像的代码,并应用过滤器。然后启动渲染添加一些粒子并渲染所有内容。

在函数drawParticles中查看我如何绘制所有内容。 imageTools具有您需要的所有图像功能。 imageTools.applyConvolutionFilter将应用您需要创建适当过滤器的任何过滤器(锐化,轮廓等等)。应用使用光子计数颜色模型,因此提供非常高质量的结果,尤其是对于模糊类型效果。 (虽然为了锐化你可能想要进入并改变RGB值的平方,我个人喜欢它其他没有)

模糊滤镜速度不快,如果将其应用于较大的图像最好将其分解,以免阻止页面执行。

获得模糊的一种便宜方法是将图像复制到模糊到自身的较小版本,例如1/4然后将其缩放回正常尺寸,画布将在图像上应用双线性滤镜以产生模糊效果。不是最好的质量,但在大多数情况下,它与我提出的更复杂的模糊无法区分。

<强>更新 更改代码,使粒子有一点3dFX,以显示模糊可以达到更大的比例。蓝色粒子为32 x 32图像,模糊为9像素,模糊图像为50 x 50像素。

var imageTools = (function () {
    var tools = {
        canvas : function (width, height) {  // create a blank image (canvas)
            var c = document.createElement("canvas");
            c.width = width;
            c.height = height;
            return c;
        },
        createImage : function (width, height) {
            var image = this.canvas(width, height);
            image.ctx = image.getContext("2d");
            return image;
        },
        image2Canvas : function (img) {
            var image = this.canvas(img.width, img.height);
            image.ctx = image.getContext("2d");
            image.drawImage(img, 0, 0);
            return image;
        },        
        padImage : function(img,amount){
            var image = this.canvas(img.width + amount * 2, img.height + amount * 2);
            image.ctx = image.getContext("2d");
            image.ctx.drawImage(img, amount, amount);
            return image;
        },
        getImageData : function (image) {
            return (image.ctx || (this.image2Canvas(image).ctx)).getImageData(0, 0, image.width, image.height);
        },
        putImageData : function (image, imgData){
            (image.ctx || (this.image2Canvas(image).ctx)).putImageData(imgData,0, 0);            
            return image;
            
        },
        createBlurConvolutionArray : function(options){
            var i, j, d;  // misc vars
            var filterArray = [];  // the array to create
            var size = options.size === undefined ? 3: options.size;  // array size 
            var center = Math.floor(size / 2);   // center of array
            // the power ? needs descriptive UI options
            var power = options.power === undefined ? 1: options.power;
            // dist to corner
            var maxDist = Math.sqrt(center * center + center * center);
            var dist = 0;    // distance sum
            var sum = 0;     // weight sum
            var centerWeight;   // center calculated weight
            var totalDistance;   // calculated total distance from center
            // first pass get the total distance
            for(i = 0; i < size; i++){
                for(j = 0; j < size; j++){
                    d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
                    d = Math.pow(d,power)
                    dist += d;
                }
            }
            totalDistance = dist;   // total distance to all points;
            // second pass get the total weight of all but center
            for(i = 0; i < size; i++){
                for(j = 0; j < size; j++){
                    d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
                    d = Math.pow(d,power)
                    d = d/totalDistance;
                    sum += d;
                }
            }
            var scale = 1/sum;
            sum = 0;  // used to check
            for(i = 0; i < size; i++){
                for(j = 0; j < size; j++){
                    d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
                    d = Math.pow(d,power)
                    d = d/totalDistance;
                    filterArray.push(d*scale);
                }
            }
            return filterArray;
        },
        applyConvolutionFilter : function(image,filter){
            imageData = this.getImageData(image);
            imageDataResult = this.getImageData(image);
            var w = imageData.width;
            var h = imageData.height;
            var data = imageData.data;
            var data1 = imageDataResult.data;
            var side = Math.round(Math.sqrt(filter.length));
            var halfSide = Math.floor(side/2);            
            var r,g,b,a,c;
            for(var y = 0; y < h; y++){
                for(var x = 0; x < w; x++){
                    var ind = y*4*w+x*4;
                    r = 0;
                    g = 0;
                    b = 0;
                    a = 0;
                    for (var cy=0; cy<side; cy++) {
                        for (var cx=0; cx<side; cx++) {
                            var scy = y + cy - halfSide;
                            var scx = x + cx - halfSide;
                            if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
                                var srcOff = (scy*w+scx)*4;
                                var wt = filter[cy*side+cx];
                                r += data[srcOff+0] * data[srcOff+0] * wt;
                                g += data[srcOff+1] * data[srcOff+1] * wt;
                                b += data[srcOff+2] * data[srcOff+2] * wt;
                                a += data[srcOff+3] * data[srcOff+3] * wt;
                            }
                        }
                    }     
    
    
                    data1[ind+0] = Math.sqrt(Math.max(0,r));
                    data1[ind+1] = Math.sqrt(Math.max(0,g));
                    data1[ind+2] = Math.sqrt(Math.max(0,b));
                    data1[ind+3] = Math.sqrt(Math.max(0,a));
                }
            }
            return this.putImageData(image,imageDataResult);
        }

    };
    return tools;
})();

/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars 
var canvas, ctx;
var globalTime = 0; 
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
    var c,cs;
    cs = (c = document.createElement("canvas")).style; 
    c.id = CANVAS_ELEMENT_ID;    
    cs.position = "absolute";
    cs.top = cs.left = "0px";
    cs.zIndex = 1000;
    document.body.appendChild(c); 
    return c;
}
resizeCanvas = function () {
    if (canvas === U) { canvas = createCanvas(); }
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight; 
    ctx = canvas.getContext("2d"); 
    if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){ 
   cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; 
   if(particles && particles.length > 0){
      particles.length = 0;
   }
}



resizeCanvas(); // create and size canvas
window.addEventListener("resize",resizeCanvas); // add resize event

const IMAGE_SIZE = 32;
const IMAGE_SIZE_HALF = 16;
const GRAV = 2001;
const NUM_PARTICLES = 90;
var background = imageTools.createImage(8,8);
var grad = ctx.createLinearGradient(0,0,0,8);
grad.addColorStop(0,"#000");
grad.addColorStop(1,"#048");
background.ctx.fillStyle = grad;
background.ctx.fillRect(0,0,8,8);
var circle = imageTools.createImage(IMAGE_SIZE,IMAGE_SIZE);
circle.ctx.fillStyle = "#5BF";
circle.ctx.arc(IMAGE_SIZE_HALF, IMAGE_SIZE_HALF, IMAGE_SIZE_HALF -2,0, Math.PI * 2);
circle.ctx.fill();
var blurFilter = imageTools.createBlurConvolutionArray({size:9,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
var blurCircle = imageTools.padImage(circle,9);
blurCircle = imageTools.applyConvolutionFilter(blurCircle,blurFilter)
var sun =  imageTools.createImage(64,64);
grad = ctx.createRadialGradient(32,32,0,32,32,32);
grad.addColorStop(0,"#FF0");
grad.addColorStop(1,"#A40");
sun.ctx.fillStyle = grad;
sun.ctx.arc(32,32,32 -2,0, Math.PI * 2);
sun.ctx.fill();
var sunBlur = imageTools.padImage(sun,17);
blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
sunBlur = imageTools.applyConvolutionFilter(sunBlur,blurFilter);

var particles = [];
var createParticle = function(x,y,dx,dy){
    var dir = Math.atan2(y-ch,x-cw);
    var dist = Math.sqrt(Math.pow(y-ch,2)+Math.pow(x-cw,2));
    var v = Math.sqrt(GRAV / dist); // get apporox orbital speed
    return {
        x : x,
        y : y,
        dx : dx + Math.cos(dir + Math.PI/2) * v, // set orbit speed at tangent
        dy : dy + Math.sin(dir + Math.PI/2) * v, 
        s : (Math.random() + Math.random() + Math.random())/4 + 0.5, // scale
        v : (Math.random() + Math.random() + Math.random()) / 3 + 2, // glow vary rate
    };
}
var depthSort = function(a,b){
    return b.y - a.y;
}
var updateParticles = function(){
    var i,p,f,dist,dir;
    for(i = 0; i < particles.length; i ++){
        p = particles[i];
        dist = Math.sqrt(Math.pow(cw-p.x,2)+Math.pow(ch-p.y,2));
        dir = Math.atan2(ch-p.y,cw-p.x);
        f = GRAV * 1 / (dist * dist);
        p.dx += Math.cos(dir) * f;
        p.dy += Math.sin(dir) * f;
        p.x += p.dx;
        p.y += p.dy;
        p.rx = ((p.x - cw ) / (p.y + h)) * h + cw;
        p.ry = ((p.y - ch ) / (p.y + h)) * h * -0.051+ ch;
        //p.ry = ((h-p.y) - ch)  * 0.1 + ch;
        p.rs = (p.s / (p.y + h)) * h
    }
    particles.sort(depthSort)
}

var drawParticles = function(){
    var i,j,p,f,dist,dir;
    // draw   behind the sun
    for(i = 0; i < particles.length; i ++){
        p = particles[i];
        if(p.y - ch < 0){
            break;
        }
        ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
        ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
        
    }
    // draw glow for behind the sun
    ctx.globalCompositeOperation = "screen";
    var iw = -blurCircle.width/2;
    for(j = 0; j < i; j ++){
        p = particles[j];
        ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) *  0.6 + 0.4;
        var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) *  0.6 + 0.6;
        ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
        ctx.drawImage(blurCircle,iw,iw);
        // second pass to intensify the glow
        ctx.globalAlpha = 0.7;
        ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
        ctx.drawImage(blurCircle,iw,iw);
    }
   
    // draw the sun
    ctx.globalCompositeOperation = "source-over";      
    ctx.globalAlpha = 1;
    ctx.setTransform(1,0,0,1,cw,ch);
    ctx.drawImage(sun,-sun.width/2,-sun.height/2);
    ctx.globalAlpha = 1;
    ctx.globalCompositeOperation = "screen";    
    ctx.setTransform(1,0,0,1,cw,ch);
    ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
    var scale = Math.sin(globalTime / 100) *0.5 + 1;
    ctx.globalAlpha = (Math.cos(globalTime / 100) + 1) * 0.2 + 0.4;;
    ctx.setTransform(1 + scale,0,0,1 + scale,cw,ch);
    ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
    
    
    ctx.globalAlpha = 1;
    ctx.globalCompositeOperation = "source-over";      
    // draw   in front the sun
    for(j = i; j < particles.length; j ++){
        p = particles[j];
        if(p.y > -h){ // don't draw past the near view plane
            ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
            ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
        }
        
    }
    ctx.globalCompositeOperation = "screen";
    var iw = -blurCircle.width/2;
    for(j = i; j < particles.length; j ++){
        p = particles[j];
        if(p.y > -h){ // don't draw past the near view plane
            ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) *  0.6 + 0.4;
            var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) *  0.6 + 0.6;
            ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
            ctx.drawImage(blurCircle,iw,iw);
            // second pass to intensify the glow
            ctx.globalAlpha = 0.7;
            ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
            ctx.drawImage(blurCircle,iw,iw);
        }
    }

    ctx.globalCompositeOperation = "source-over";    
}


var addParticles = function(count){
    var ww = (h-10)* 2;
    var cx = cw - ww/2;
    var cy = ch - ww/2;
    for(var i = 0; i < count; i ++){
        particles.push(createParticle(cx + Math.random() * ww,cy + Math.random() * ww, Math.random() - 0.5, Math.random() - 0.5));
    }
}


function display(){  // put code in here
    if(particles.length === 0){
        addParticles(NUM_PARTICLES);
    }
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    ctx.drawImage(background,0,0,w,h)
    updateParticles();
    drawParticles();

    ctx.globalAlpha = 1;
    ctx.globalCompositeOperation = "source-over";       
}
function update(timer){ // Main update loop
    globalTime = timer;
    display();  // call demo code
    requestAnimationFrame(update);
}
requestAnimationFrame(update);

/** SimpleFullCanvasMouse.js end **/