【问题标题】:HTML Canvas: inner shadow for circle shapes (planets)HTML Canvas:圆形(行星)的内部阴影
【发布时间】:2017-07-28 03:32:21
【问题描述】:

我的目的是在圆形形状内绘制半圆形内部阴影,代表行星围绕恒星移动(这是我正在开展的教育计划的一部分)。

经过多次尝试,这对我来说几乎是有效的:

  1. 画一个圆形(行星),然后在上面画一个包含实际阴影的大圆圈。

2. 使用组合选项“ctx.globalCompositeOperation='source-atop';”要绘制更大的圆圈,它只会绘制与现有内容重叠的部分:

但问题是 any 行星会与 any 阴影圈重叠,因此,如您所见,当行星与较大的阴影重叠时,它会完全变暗。

有没有办法让它绘制特定内容(形状)的重叠区域?

或者,您知道更好的方法吗?请记住,我必须以从行星到光源的特定角度绘制阴影。

提前致谢!

【问题讨论】:

    标签: javascript canvas shadow globalcompositeoperation


    【解决方案1】:

    预渲染阴影。

    太阳系阴影的酷解决方案。

    有些设备不喜欢渲染阴影,渲染期间的所有遮罩操作都会影响您可能添加的任何其他 FX。

    制作阴影的一种方法是在开始时为每个行星渲染一个阴影。掩盖它,使其完美地适应地球。在动画过程中,只需绘制行星,然后旋转阴影图像使其面向太阳并调用 drawImage 即可获得与您相同的效果,而且速度要快很多倍。

    示例

    函数createShadow 为行星创建自定义阴影图像并将其作为planet.shadow 添加到行星对象。 drawPlanet 函数首先绘制行星,然后使用普通的source-over 合成在其上绘制阴影。

        var canvas = document.createElement("canvas");
        canvas.width = canvas.height = 1024;
        var ctx = canvas.getContext("2d");
        document.body.appendChild(canvas);
        
        const shadowImageSafeEdge = 2; // pixel safe border around shadow image
        const shadowBlur = 0.8; // fraction of planet radius
        var sun = {
            x : canvas.width /2,
            y : canvas.height / 2,
            radius : 80,
            color : "yellow",
        }
        var sunGrad = ctx.createRadialGradient(0, 0, sun.radius/4, 0, 0, sun.radius);
        sunGrad.addColorStop(0,"#FF7");
        sunGrad.addColorStop(0.6,"#FF4");
        sunGrad.addColorStop(0.8,"#FF0");
        sunGrad.addColorStop(1,"#DC0");
        sun.color = sunGrad;
    
        function rInt(min,max){
            return Math.floor((max-min) * Math.random() + min);
        }
        function randCol(hue){
            var col = "hsl(";
            col += Math.floor(hue + rInt(-30,30) + 360) % 360;
            col += ",";
            col += Math.floor(80 + rInt(-20,20) + 100) % 100;
            col += "%,";
            col += Math.floor(50 + rInt(-10,10) + 100) % 100;
            col += "%)";
            return col;
        }
        // creates a planet at orbit distance from sun    
        function createPlanet(orbit){
            var planet = {
                radius : Math.random() * 20 + 5,
                orbitDist : orbit,  // dist from sun
                orbitPos : Math.random() * Math.PI * 2,
                shadow : null,
            }
            planet.color = randCol(rInt(280, 360));
            planet.shadow = createShadow(planet);
            return planet;        
        }
        // creates a shadow image that fits the planet
        function createShadow(planet){    
            var r = planet.radius;
            var s = shadowImageSafeEdge;
            var planetShadow = document.createElement("canvas");
            planetShadow.width = planetShadow.height = r * s + s * 2; // a little room to stop hard edge if zooming
            var ctx = planetShadow.ctx = planetShadow.getContext("2d");
            ctx.shadowBlur = r * shadowBlur  ;
            ctx.shadowOffsetX = ctx.shadowOffsetY = 0;
            ctx.lineWidth = r * 2 - r * (1 - shadowBlur / 2);
            ctx.strokeStyle = ctx.shadowColor = "rgba(0,0,0,1)";
            ctx.beginPath();
            ctx.arc(-planet.orbitDist - r,r + s, planet.orbitDist + r * 2 + r * (shadowBlur /0.85) + s, 0, Math.PI * 2);
            ctx.stroke();
            ctx.stroke();
            ctx.stroke();
            ctx.shadowColor = "rgba(0,0,0,0)";
            ctx.globalCompositeOperation = "destination-in";
            ctx.beginPath();
            ctx.arc(r + s, r + s, r, 0, Math.PI * 2);  // sun will be along x axis
            ctx.fill();
            ctx.globalCompositeOperation = "source-over";
            return planetShadow;
        }
    
        // draws the planet and the shadow
        function drawPlanet(planet){
             var xdx = Math.cos(planet.orbitPos); 
             var xdy = Math.sin(planet.orbitPos);
             var x = xdx * planet.orbitDist + sun.x;
             var y = xdy * planet.orbitDist + sun.y;
             ctx.setTransform(1,0,0,1,x,y);
             ctx.fillStyle = planet.color;
             ctx.beginPath();
             ctx.arc(0,0,planet.radius,0,Math.PI * 2);
             ctx.fill();
    
             // set transform so that shadow faces away from the sun
             ctx.globalAlpha = 0.8;
             ctx.setTransform(xdx,xdy,-xdy,xdx,x,y);
             ctx.drawImage(planet.shadow,-planet.radius - 2,-planet.radius - 2);
             ctx.globalAlpha =1;
         }
         // let you guess what this function does
         function drawSun(){
             ctx.fillStyle = sun.color;
             ctx.setTransform(1,0,0,1,sun.x,sun.y);
             ctx.beginPath();
             ctx.arc(0,0,sun.radius,0,Math.PI * 2);
             ctx.fill();
         }
         // array of planets and create them
         var planets = [];
         (function(){
            var i = 10;
            while(i-- >1){
                planets.push(
                    createPlanet(
                       rInt( 60 + i * 40,i * 40 + 100)
                    )
                );
            }  
         }());
         // gradient for background
         var backGrad = ctx.createRadialGradient(512, 512, sun.radius, 512, 512, Math.sqrt(512 * 512 * 2));
         backGrad.addColorStop(0,"#B9E");
         backGrad.addColorStop(0.025,"#96A");
         backGrad.addColorStop(1,"#624");
    
         // main render loop
         function render(time){
             ctx.setTransform(1,0,0,1,0,0); // reset transform
             ctx.fillStyle = backGrad;   
             ctx.fillRect(0,0,1024,1024);  // clear
             drawSun();  
             for(var i = 0; i < planets.length; i++){ // draw all planets
                 planets[i].orbitPos += Math.sqrt(10 / Math.pow(planets[i].orbitDist, 2));
                 drawPlanet(planets[i]);
             }
             requestAnimationFrame(render);
         }
         requestAnimationFrame(render);

    【讨论】:

    • 这是一个很好的解决方案,我会尝试将此方法适应我的代码,谢谢!
    【解决方案2】:

    在像这样在行星上绘制“阴影”之前尝试调用clip 方法(和相关代码)。

    const ctx = canvas.getContext("2d");
    
    //draw planet
    ctx.beginPath();
    ctx.arc(100, 100, 80, 0, Math.PI*2);
    ctx.fillStyle = "aqua";
    ctx.fill();
    //save non-clipped state.
    ctx.save();
    //clip range by planet area.
    ctx.clip();
    //draw shadow
    ctx.beginPath();
    ctx.arc(200, 200, 200, 0, Math.PI*2);
    ctx.lineWidth = 100;
    ctx.stroke();
    //dispose clip range.
    ctx.restore();
    &lt;canvas id="canvas" width="200" height="200"&gt;&lt;/canvas&gt;

    【讨论】:

      【解决方案3】:

      我会使用几个画布。我会维护一个“主”画布并将单个元素绘制到其他画布,然后将它们混合到主画布中。

      这个问题有关于将一个画布混合到另一个画布的信息:Combining two or more Canvas elements with some sort of blending

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-16
        • 1970-01-01
        • 2018-05-27
        • 1970-01-01
        • 1970-01-01
        • 2015-10-17
        相关资源
        最近更新 更多