Javascript Canvas将径向渐变应用于细分?

时间:2015-11-13 05:32:09

标签: javascript html5 canvas

我正在尝试为 HTML5 画布中的 2D游戏创建阴影系统。现在,我正在渲染我的阴影:

function drawShadows(x, y, width) {
    if (shadowSprite == null) {
        shadowSprite = document.createElement('canvas');
        var tmpCtx = shadowSprite.getContext('2d');
        var shadowBlur = 20;
        shadowSprite.width = shadowResolution;
        shadowSprite.height = shadowResolution;
        var grd = tmpCtx.createLinearGradient(-(shadowResolution / 4), 0,
            shadowResolution, 0);
        grd.addColorStop(0, "rgba(0, 0, 0, 0.1)");
        grd.addColorStop(1, "rgba(0, 0, 0, 0)");
        tmpCtx.fillStyle = grd;
        tmpCtx.shadowBlur = shadowBlur;
        tmpCtx.shadowColor = "#000";
        tmpCtx.fillRect(0, 0, shadowResolution, shadowResolution);
    }
    graph.save();
    graph.rotate(sun.getDir(x, y));
    graph.drawImage(shadowSprite, 0, -(width / 2), sun.getDist(x, y), width);
    graph.restore();
}

这会渲染一个具有线性渐变的立方体,该渐变从黑色渐变到alpha 0。 然而,这不会产生真实的结果,因为它总是一个矩形。以下是描述问题的说明:

enter image description here

抱歉,我不是很有艺术气质。绘制梯形形状不是问题。 (见蓝色)。问题是我仍然有一个渐变。是否可以用渐变绘制这样的形状?

1 个答案:

答案 0 :(得分:1)

画布非常灵活。几乎任何事都有可能。此示例绘制正在投射的光。但它可以很容易地反过来。将阴影绘制为渐变。

如果您追求的是真实感,那么不要渲染光照(或阴影)的渐变,而是使用创建的形状来设置剪裁区域,然后渲染精确的光照和阴影解决方案。

使用lineTo和渐变,您可以根据自己的意愿创建任何形状和渐变。另外,要获得最佳结果,请使用globalCompositeOperation,因为它们有各种各样的过滤器。

该演示只展示了如何混合渐变和阴影贴图。 (非常基本没有实现递归,阴影只是近似值。)

var canvas = document.getElementById("canV");  
var ctx = canvas.getContext("2d");

var mouse = {
    x:0,
    y:0,
};
function mouseMove(event){
    mouse.x = event.offsetX;  mouse.y = event.offsetY; 
    if(mouse.x === undefined){ mouse.x = event.clientX;  mouse.y = event.clientY;}    
}
// add mouse controls
canvas.addEventListener('mousemove',mouseMove);

var boundSize = 10000;  // a number....
var createImage = function(w,h){ // create an image
    var image;
    image = document.createElement("canvas");
    image.width = w;
    image.height = h;
    image.ctx = image.getContext("2d");
    return image;
}  
var directionC = function(x,y,xx,yy){           // this should be inLine but the angles were messing with my head 
    var a;                                      // so moved it out here
    a = Math.atan2(yy - y, xx - x);             // for clarity and the health of my sanity
    return (a + Math.PI * 2) % (Math.PI * 2); // Dont like negative angles.
}

// Create background image
var back = createImage(20, 20);
back.ctx.fillStyle = "#333";
back.ctx.fillRect(0, 0, 20, 20);
// Create background image
var backLight = createImage(20, 20);
backLight .ctx.fillStyle = "#ACD";
backLight .ctx.fillRect(0, 0, 20, 20);
// create circle image
var circle = createImage(64, 64);
circle.ctx.fillStyle = "red";
circle.ctx.beginPath();
circle.ctx.arc(32, 32, 30, 0, Math.PI * 2);
circle.ctx.fill();
// create some circles semi random
var circles = [];
circles.push({
    x : 200 * Math.random(),
    y : 200 * Math.random(),
    scale : Math.random() * 0.8 + 0.3,
});
circles.push({
    x : 200 * Math.random() + 200,
    y : 200 * Math.random(),
    scale : Math.random() * 0.8 + 0.3,
});
circles.push({
    x : 200 * Math.random() + 200,
    y : 200 * Math.random() + 200,
    scale : Math.random() * 0.8 + 0.3,
});
circles.push({
    x : 200 * Math.random(),
    y : 200 * Math.random() + 200,
    scale : Math.random() * 0.8 + 0.3,
});

// shadows on for each circle;
var shadows = [{},{},{},{}];

var update = function(){
    var c, dir, dist, x, y, x1, y1, x2, y2, dir1, dir2, aAdd, i, j, s, s1 ,nextDir, rev, revId;

    rev = false;  // if inside a circle reverse the rendering.

    // set up the gradient at the mouse pos
    var g = ctx.createRadialGradient(mouse.x, mouse.y, canvas.width * 1.6, mouse.x, mouse.y, 2);

    // do each circle and work out the two shadow lines coming from it.
    for(var i = 0; i < circles.length; i++){
        c = circles[i];
        dir = directionC(mouse.x, mouse.y, c.x, c.y);
        dist = Math.hypot(mouse.x - c.x, mouse.y - c.y);
        // cludge factor. Could not be bother with the math as the light sourse nears an object
        if(dist < 30* c.scale){ 
            rev = true;
            revId = i;
        }
        aAdd = (Math.PI / 2) * (0.5 / (dist - 30 * c.scale));
        x1 = Math.cos(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
        y1 = Math.sin(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
        x2 = Math.cos(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
        y2 = Math.sin(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
        // direction of both shadow lines
        dir1 = directionC(mouse.x, mouse.y, c.x + x1, c.y + y1);
        dir2 = directionC(mouse.x, mouse.y, c.x + x2, c.y + y2);

        // create the shadow object to hold details
        shadows[i].dir = dir;
        shadows[i].d1 = dir1;
        if (dir2 < dir1) { // make sure second line is always greater
            dir2 += Math.PI * 2;
        }
        shadows[i].d2 = dir2;
        shadows[i].x1 = (c.x + x1); // set the shadow start pos
        shadows[i].y1 = (c.y + y1);
        shadows[i].x2 = (c.x + x2); // for both lines
        shadows[i].y2 = (c.y + y2);
        shadows[i].circle = c; // ref the circle
        shadows[i].dist = dist; // set dist from light
        shadows[i].branch1 = undefined; //.A very basic tree for shadows that interspet other object
        shadows[i].branch2 = undefined; //
        shadows[i].branch1Dist = undefined;
        shadows[i].branch2Dist = undefined;
        shadows[i].active = true; // false if the shadow is in a shadow
        shadows[i].id = i;
    }
    shadows.sort(function(a,b){  // sort by distance from light
        return a.dist - b.dist;
    });
    // cull shdows with in shadows and connect circles with joined shadows
    for(i = 0; i < shadows.length; i++){
        s = shadows[i];
        for(j = i + 1; j < shadows.length; j++){
            s1 = shadows[j];
            if(s1.d1 > s.d1 && s1.d2 < s.d2){ // if shadow in side another
                s1.active = false;  // cull it
            }else
            if(s.d1 > s1.d1 && s.d1 < s1.d2){ // if shodow intercepts going twards light
                s1.branch1 = s;
                s.branch1Dist = s1.dist - s.dist;
                s.active = false;
            }else
            if(s.d2 > s1.d1 && s.d2 < s1.d2){  // away from light
                s.branch2 = s1;
                s.branch2Dist = s1.dist - s.dist;
                s1.active = false;
            }
        }        
    }
    // keep it quick so not using filter 
    // filter culled shadows
    var shadowsShort = [];
    for (i = 0; i < shadows.length; i++) {
        if ((shadows[i].active && !rev) || (rev && shadows[i].id === revId)) {  // to much hard work makeng shadow from inside the circles. Was a good idea at the time. But this i just an example after all;
            shadowsShort.push(shadows[i])
        }
    }
    // sort shadows in clock wise render order
    if(rev){
        g.addColorStop(0.3, "rgba(210,210,210,0)");
        g.addColorStop(0.6, "rgba(128,128,128,0.5)");
        g.addColorStop(1, "rgba(0,0,0,0.9)");
        shadowsShort.sort(function(a,b){
            return b.dir - a.dir;
        });
        // clear by drawing background image.
        ctx.drawImage(backLight, 0, 0, canvas.width, canvas.height);

    }else{
        g.addColorStop(0.3, "rgba(0,0,0,0)");
        g.addColorStop(0.6, "rgba(128,128,128,0.5)");
        g.addColorStop(1, "rgba(215,215,215,0.9)");
        shadowsShort.sort(function(a,b){
            return a.dir - b.dir;
        });
        // clear by drawing background image.
        ctx.drawImage(back, 0, 0, canvas.width, canvas.height);
    }
    
    // begin drawin the light area
    ctx.fillStyle = g; // set the gradient as the light
    ctx.beginPath();
    for(i = 0; i < shadowsShort.length; i++){  // for each shadow move in to the light across the circle and then back out away from the light
        s = shadowsShort[i];
        x = s.x1 + Math.cos(s.d1) * boundSize;
        y = s.y1 + Math.sin(s.d1) * boundSize;
        if (i === 0) { // if the start move to..
            ctx.moveTo(x, y);
        } else {
            ctx.lineTo(x, y);
        }
        ctx.lineTo(s.x1, s.y1);
        if (s.branch1 !== undefined) { // if braching. (NOTE this is not recursive. the correct solution would to math this a function and use recursion to climb in  an out)
            s = s.branch1;
            x = s.x1 + Math.cos(s.d1) * s.branch1Dist;
            y = s.y1 + Math.sin(s.d1) * s.branch1Dist;
            ctx.lineTo(x, y);
            ctx.lineTo(s.x1, s.y1);
        }
        ctx.lineTo(s.x2, s.y2);
        if (s.branch2 !== undefined) {
            x = s.x2 + Math.cos(s.d2) * s.branch2Dist;
            y = s.y2 + Math.sin(s.d2) * s.branch2Dist;
            ctx.lineTo(x, y);
            s = s.branch2;
            ctx.lineTo(s.x2, s.y2);
        }
        x = s.x2 + Math.cos(s.d2) * boundSize;
        y = s.y2 + Math.sin(s.d2) * boundSize;
        ctx.lineTo(x, y);
        // now fill in the light between shadows
        s1 = shadowsShort[(i + 1) % shadowsShort.length];
        nextDir = s1.d1;
        if(rev){
            if (nextDir > s.d2) {
                nextDir -= Math.PI * 2
            }
        }else{
            if (nextDir < s.d2) {
                nextDir += Math.PI * 2
            }
        }
        x = Math.cos((nextDir+s.d2)/2) * boundSize + canvas.width / 2;
        y = Math.sin((nextDir+s.d2)/2) * boundSize + canvas.height / 2;
        ctx.lineTo(x, y);
    }
    // close the path.
    ctx.closePath();
    // set the comp to lighten  or multiply
    if(rev){
        ctx.globalCompositeOperation ="multiply";
    }else{
        ctx.globalCompositeOperation ="lighter";
    }
    // draw the gradient
    ctx.fill()
    ctx.globalCompositeOperation ="source-over";

    // draw the circles
    for (i = 0; i < circles.length; i++) {
        c = circles[i];
        ctx.drawImage(circle, c.x - 32 * c.scale, c.y - 32 * c.scale, 64 * c.scale, 64 * c.scale);
    }
    // feed the herbervors.

    window.requestAnimationFrame(update);
}
update();
.canC { width:400px;  height:400px;}
<canvas class="canC" id="canV" width=400 height=400></canvas>